make后gcc出现不全_Linux零基础:C语言和gcc
GCC(GNU Compiler Collection,GNU編譯器套裝),原名為GNU C語言編譯器(GNU C Compiler),只能處理C語言。但其很快擴展,變得可處理C++,后來又擴展為能夠支持更多編程語言,如Fortran、Pascal、Objective -C、Java、Ada、Go以及各類處理器架構上的匯編語言等,所以改名GNU編譯器套件(GNU Compiler Collection)。
首先,討論一下,什么是編譯器?
編譯器是用于將高級語言代碼翻譯成機器語言的程序。那什么是高級語言和機器語言?機器語言Machine Language是一種低級語言。機器語言是計算機唯一能接受和執行的語言。機器語言由二進制碼組成,每一串二進制碼叫做一條指令。一條指令規定了計算機執行的一個動作。一臺計算機所能懂得的指令的全體,叫做這個計算機的指令系統。不同型號的計算機的指令系統不同。
指令是用0和1組成的一串代碼,它們有一定的位數,并分成若干段,各段的編碼表示不同的含義,例如某臺計算機字長為16位,即有16個二進制數組成一條指令或其它信息。16個0和1可組成各種排列組合,通過線路變成電信號,讓計算機執行各種不同的操作。
如某種計算機的指令為1011011000000000,它表示讓計算機進行一次加法操作;而指令1011010100000000則表示進行一次減法操作。它們的前八位表示操作碼,而后八位表示地址碼。從上面兩條指令可以看出,它們只是在操作碼中從左邊第0位算起的第6和第7位不同。這種機型可包含256(=2^8)個不同的指令。
機器語言或稱為二進制代碼語言,計算機可以直接識別,不需要進行任何翻譯。每臺機器的指令,其格式和代碼所代表的含義都是硬性規定的,故稱之為面向機器的語言,也稱為機器語言。它是第一代的計算機語言。機器語言對不同型號的計算機來說一般是不同的。使用機器語言編寫程序是一種相當煩瑣的工作,既難于記憶也難于操作,編寫出來的程序全是由0和1的數字組成,直觀性差、難以閱讀。不僅難學、難記、難檢查、又缺乏通用性,給計算機的推廣使用帶來很大的障礙。
匯編語言Assembler Language(低級語言)
為了克服機器語言上述的缺點,很快就出現匯編語言。用能反映指令功能的助記符表達的計算機語言叫匯編語言。它是符號化了的機器語言。用匯編語言編寫的程序叫匯編語言源程序,計算機無法執行。必須用匯編程序把它翻譯成機器語言目標程序,計算機才能執行。這個翻譯過程稱為匯編過程。
匯編語言是用助記符表示指令功能的計算機語言。與機器語言相比,匯編語言具有以下的幾個特點:第一,它使用符號來表示操作碼和地址碼,這種符號便于記憶,稱為記憶碼。第二,匯編程序自動處理存儲分配,毋需程序員做存儲分配工作。第三,程序員可以直接書寫十進制數。
例如,要計算c=7+8,可以用如下幾條匯編命令:
標號 指令 說明
START GET 7; 把7送進累加器ACC中
ADD 8; 累加器ACC+8送進累加器ACC中
PUT C; 把累加器ACC送進C中
END STOP; 停機
使用匯編語言來編寫程序或閱讀已經編寫好的程序比起機器語言來要簡單和方便多了。這就是計算機語言發展中的第二代語言。人們使用這種助記符編寫程序后,要是計算機能夠接受,還必須把編好的程序逐條翻譯成二進制編碼的機器語言。當然,這個工作并不是由程序員來完成,而是有稱為“匯編程序”的程序自動完成的。匯編程序的功能就是把由匯編語言編寫的程序(稱為匯編語言源程序)翻譯成機器語言程序,計算機才能執行該程序。這個翻譯過程稱為匯編。
匯編語言比起機器語言在很多方面都有很大的優越性,如編寫容易、修改方便、閱讀簡單、程序清楚等,但在計算機語言系統中,把匯編語言仍然列入“低級語言”的范疇,它仍然是屬于面向機器的語言,也就是說,不同的計算機可以有不同的指令集。
高級語言(High-level language)
機器語言和匯編語言都是面向機器的,高級語言是面向用戶的。到了50年代中期,出現程序設計的高級語言如Fortran、Algol60,以及后來的PL/l、Pascal,再到C、C++、Go等,算法的程序表達才產生一次大的飛躍。用高級語言編寫的程序叫做高級語言源程序,必須翻譯成機器語言目標程序才能被計算機執行。
程序設計語言從機器語言到高級語言的抽象,帶來的主要好處是:
- 高級語言接近算法語言,易學、易掌握,一般工程技術人員只要幾周時間的培訓就可以勝任程序員的工作;
- 高級語言為程序員提供了結構化程序設計的環境和工具,使得設計出來的程序可讀性好,可維護性強,可靠性高;
- 高級語言遠離機器語言,與具體的計算機硬件關系不大,因而所寫出來的程序可移植性好,重用率高;
由于把繁雜瑣碎的事務交給了編譯器去做,所以自動化程度高,開發周期短,且程序員得到解脫,可以集中時間和精力去從事更為重要的創造性勞動,以提高程序的質量。
GCC使用
我們將從頭開始一步一步地做,以便理解編譯過程,了解為了制作可執行文件需要做些什么,按什么順序做。步驟(以及所用工具)如下: 預編譯 (gcc -E)、 編譯 (gcc)、 匯編 (as)以及連接 (ld)。
首先,我們應該知道如何調用編譯器。實際上,這很簡單。從hello world程序開始。
#include <stdio.h> int main(){printf("Hello World!/n"); }把這個文件保存為 hello.c。 在命令行下編譯它:
gcc hello.c在默認情況下,C編譯器將生成一個名為 a.out 的可執行文件。可以鍵入如下命令運行它:
./a.out每一次編譯程序時,新的 a.out 將覆蓋原來的程序。你無法知道是哪個程序創建了 a.out。我們可以通過使用 -o 編譯選項,告訴 gcc我們想把可執行文件叫什么名字。我們將把這個程序叫做 game,我們可以使用任何名字,因為C沒有Java那樣的命名限制。
gcc -o hello hello.c
./hello
Hello World
gcc編譯程序過程:
在使用gcc編譯程序時,編譯過程可以為4個階段:
(1)預處理:(Pre-Processing)
(2)編譯:(Compiling)
(3)匯編:(Assembling)
(4)鏈接:(Linking)
1. 預處理(Preprocess):以源文件作為輸入,刪除其中的注釋,解析其中以#開頭的行(#include, #define, #if/ifdef/ifndef/elif/else 等),輸出預處理后源文件(.c)
2. 編譯(Compile):以預處理后源文件作為輸入,經過詞法分析,語法分析,語義分析,中間代碼生成與優化,目標代碼生成,輸出與處理器體系(x86/arm)相關的匯編源文件(.s)
3. 匯編(Assemble):以匯編源文件作為輸入,執行匯編,生成與操作系統相關的目標文件(.o, .obj)
4. 鏈接(Link):以目標文件(全部)、庫文件為輸入,解析未定義的符號引用,將目標文件中的占位符替換為符號的地址,完成程序中各目標文件的地址空間的組織(重定位),輸出可執行二進制文件
gcc的常用選項
在使用gcc編譯器的時候,我們必須給出一系列必要的選項和文件名。gcc編譯器的選項有100多個,其中很多參數一般是用不到的。另外,我們可以通過使用man gcc / info gcc來詳細了解gcc的所有選項。
gcc [options] [filenames]
其中options就是編譯器所需要的選項,filenames給出相關的文件名。
- -c:只編譯,不鏈接生成可以執行文件,編譯器值是由輸入的.c等為后綴的源文件生成.o為后綴的目標文件,通常用于編譯不包含主程序的子程序文件。
- -o output_file:確定輸出文件的名稱為output_filename,同時這個名稱不能和源文件同名。如果不給出這個選項,gcc就默認將輸出的可執行文件命名為a.out。
- -g:產生調試器gdb所必須的符號信息,要對源代碼進行調試,就必須在編譯程序是加入這個選項。
- -O:對程序進行優化編譯、鏈接,采用這個選項,整個源代碼會在編譯、鏈接過程中進行優化處理,這樣產生的可執行文件效率較高,但是,編譯、鏈接的速度就相應地要慢一些。
- -O2:比-O更好的優化編譯、鏈接,當然整個編譯、鏈接過程會更慢。
- -Wall:輸出所有警告信息,在編譯的過程中如果gcc遇到一些認為可能會發生錯誤的地方就會提出一些相應的警告和提升信息。提升注意這個地方是不是有什么錯誤。
- -w:關閉所有的警告,建議不要使用此選項。
使用-E選項
可以使用vi命令查看hello.i的結果。
gcc -S hello.i -o hello.s
gcc -c hello.s –o hello.o
gcc hello.o –o hello
最終生成了可執行的代碼hello。
接下來,我們用C語言寫一點代碼,結合C語言來學習一下網絡知識。
套接字(socket)允許在相同或不同的機器上的兩個不同進程之間進行通信。更準確地說,它是使用標準Unix文件描述符與其他計算機通信的一種方式。在Unix中,每個I/O操作都是通過寫入或讀取文件描述符來完成的。文件描述符只是與打開文件關聯的整數,它可以是網絡連接、文本文件、終端或其他內容。
對于程序員來說,套接字的使用和行為很像更底層的文件描述符。這是因為對于套接字,read()和write()等命令可以像在文件和管道編程中同樣的使用。
套接字首先在BSD 2.1中引入,然后在BSD 4.2形成當前的穩定版本。現在,大多數最新的UNIX系統版本都提供了套接字功能。
套接字在哪里使用?
Unix Socket用于客戶端 - 服務器應用程序框架中。服務器是根據客戶端請求執行某些功能的過程。大多數應用程序級協議(如FTP、SMTP和POP3)都使用套接字在客戶端和服務器之間建立連接,然后交換數據。
套接字是標準的網絡連接的接口以及這張圖:
套接字類型
用戶可以使用四種類型的套接字。前兩個是最常用的,后兩個使用較少。一般假定進程僅在相同類型的套接字之間進行通信,但是也沒有限制阻止不同類型的套接字之間的通信。
- 流(stream)套接字 - 在網絡環境中保證交付。如果通過流套接字發送三個項目“A,B,C”,它們將以相同的順序 - “A,B,C”到達。這些套接字使用TCP(傳輸控制協議)進行數據傳輸。如果無法交付,發件人會收到錯誤提示。
- 數據報(Datagram)套接字 - 無法保證在網絡環境中交付。它們是無連接的,因為不需要像流套接字那樣打開連接 ,使用UDP(用戶數據報協議)。
- 原始(raw)套接字 - 使用原始套接字,用戶可以訪問底層通信協議,這些協議支持套接字抽象。這些套接字通常是面向數據報的,但它們的確切特性取決于協議提供的接口。原始套接字不適用于普通用戶;它們主要是為那些有興趣開發新通信協議的人提供的,或者是為了獲得對現有協議的一些不常見的使用。
- 順序數據包(Sequenced Packet)套接字 - 類似于流套接字。此接口僅作為網絡系統(NS)套接字抽象的一部分提供,在大多數NS應用程序中非常重要。順序數據包套接字允許用戶通過編寫原型標頭以及要發送的任何數據來操作數據包或一組數據包上的序列數據包協議(SPP)或Internet數據報協議(IDP)標頭,或者通過指定要與所有傳出數據一起使用的默認標頭,并允許用戶在傳入數據包上接收標頭。
套接字如何使用
使用socket的時候需要使用各種結構來保存有關地址和端口的信息以及其他信息。 大多數套接字函數都需要一個指向套接字地址結構的指針作為參數。通常使用四元組<源ip,源port,目的ip,目的port>來描述一個網絡連接,使用socket的時候,往往也需要數據結構來描述這些信息。
第一個數據結構是sockaddr:
struct sockaddr {unsigned short sa_family;char sa_data[14]; };這是一個通用的套接字地址結構,在大多數套接字函數調用中都需要使用它。 成員字段的說明如下。sa_family包括以下可選值。每個值代表一種地址族(address family),在基于IP的情況中,都使用AF_INET。
- AF_INET
- AF_UNIX
- AF_NS
- AF_IMPLINK
sa_data長為14字節,根據地址類型解釋協議特定地址。 對于Internet系列,我們將使用端口號+IP地址,該地址由下面定義的sockaddr_in結構表示。
第二個數據結構是sockaddr_in:
struct sockaddr_in {short int sin_family;unsigned short int sin_port;struct in_addr sin_addr;unsigned char sin_zero[8]; };其中,sin_family和sockadd的sa_family一樣,包括四個可選值:
- AF_INET
- AF_UNIX
- AF_NS
- AF_IMPLINK
sin_port是端口號,16位長,網絡字節序(network byte order);sin_addr是IP地址,32位長,網絡字節序(network byte order)。sin_zero,8個字節,設置為0。
至于為何會使用兩個數據結構sockaddr和sockaddr_in來表示地址,原因是如sa_family所指出的,socket設計之初本來就是準備支持多個地址協議的。不同的地址協議由自己不同的地址構造,譬如對于IPv4就是sockaddr_in, IPV6就是sockaddr_in6, 以及對于AF_UNIX就是sockaddr_un。sockaddr是對這些地址的上一層的抽象。另外,像sockaddr_in將地址拆分為port和IP,對編程也更友好。這樣,在講所使用的的值賦值給sockaddr_in數據結構之后,通過強制類型轉換,就可以轉換為sockaddr。當然,從sockaddr也可以強制類型轉換為sockaddr_in。
在sockaddr_in中還有一個結構體,struct in_addr,
struct in_addr {unsigned long s_addr; };就是一個32位的IP地址,同樣是網絡字節序。
關于字節序,補充一些內容:
- Little Endian - 在該方案中,低位字節存儲在起始地址(A)上,高位字節存儲在下一個地址(A + 1)上。
- Big Endian - 在該方案中,高位字節存儲在起始地址(A)上,低位字節存儲在下一個地址(A + 1)上。
為了允許具有不同字節順序約定的機器相互通信,Internet協議為通過網絡傳輸的數據指定了規范的字節順序約定。 這稱為網絡字節順序。在建立Internet套接字連接時,必須確保sockaddr_in結構的sin_port和sin_addr成員中的數據在網絡字節順序中表示。
不用擔心這幾個數據結構以及字節序,因為socket接口非常貼心地準備好了各種友好的接口。
- htons() Host to Network Short
- htonl() Host to Network Long
- ntohl() Network to Host Long
- ntohs() Network to Host Short
譬如對上面描述的過程,想要把地址200.200.200.200和端口3456綁定到一個socket,以下代碼就足夠了:
struct sockaddr_in myaddr; int s;myaddr.sin_family = AF_INET; myaddr.sin_port = htons(3456); inet_aton("200.200.200.200", &myaddr.sin_addr.s_addr);s = socket(PF_INET, SOCK_STREAM, 0); bind(s, (struct sockaddr*)myaddr, sizeof(myaddr));下面會看到,對于簡單的socket應用編程,所需要做的就是記住流程。
使用客戶端-服務器端(client-server)模型作為一個例子。server一般打開端口,被動偵聽,不需要知道客戶端的IP和端口;而client發起請求,必須知道服務器端的IP和端口。
在這個過程中,所需要用到的函數如下:
再用一張圖描述下客戶端和服務器端的流程:
接下來,我們看C/S的代碼實例。
客戶端代碼:
#include<stdio.h> #include<unistd.h> #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #include <errno.h> #include <stdlib.h> int main(){int clientfd, conn;struct sockaddr_in servaddr,cliaddr;char buff[1024];char buff2[1024];int servlen; int n;bzero(buff,1024);bzero(buff2,1024); bzero(&cliaddr,sizeof(cliaddr));cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);cliaddr.sin_family = AF_INET;cliaddr.sin_port = htons(0);clientfd = socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family = AF_INET;if(inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr)<0) printf("address error1n");//if(inet_pton(AF_INET,"192.168.116.158",&servaddr.sin_addr)<0) printf("address error1n");servaddr.sin_port = htons(2345);servlen = sizeof(servaddr);conn = connect(clientfd,(struct sockaddr *)&servaddr,servlen);if(conn < 0) printf("connect error!n");if(n=recv(clientfd,buff2,sizeof(buff2),0)>0)printf("Message %s:",buff2);printf("clientfd is %d,connfd is %d.n",clientfd,conn);while(1){while((n=read(0,buff,sizeof(buff)))>0){if(send(clientfd,buff,n,0)<0){printf("send error! %s(errno :%d)n",strerror(errno),errno);exit(0);}if((n=recv(clientfd,buff2,sizeof(buff2),0))>0){write(0,buff2,n);}}}close(clientfd);}以及服務器端代碼:
#include<stdio.h> #include<unistd.h> #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #include <errno.h> #include <stdlib.h> int main(){int listenfd, connfd;struct sockaddr_in servaddr,cliaddr;char buff[1024];int clilen; int n;listenfd = socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(2345);if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)printf("bind error!n"); listen(listenfd,10);clilen = sizeof(cliaddr);printf("serverfd is %d, connfd is %d.n",listenfd,connfd);while(1){connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);send(connfd,"Welcome to Server!n",19,0);while((n=read(connfd,buff,sizeof(buff)))>0){// printf("Received string length is %d.n",n); write(1,buff,n);n = read(0,buff,sizeof(buff));write(connfd,buff,n);}close(connfd);}close(listenfd);}編譯之后,就可以在兩個進程間進行通信了。這個簡單代碼的作用是讓客戶端和服務器端進行通信。
總結
以上是生活随笔為你收集整理的make后gcc出现不全_Linux零基础:C语言和gcc的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚拟机linux识别不了u盘_将Arch
- 下一篇: 用户与订单之间的关系_wms与oms、t