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