C語言小巧靈活,語法簡單,適合做一些小工具。如果用C語言做出各種各樣的小工具,並結合起來,整個Unix/Linux系統就是這樣來的;C語言還能用來做與硬件打交道的程序,比如操作系統,單片機,ARM嵌入式,Arduino;C語言還用來做一些有高性能要求的應用程序,比如Nginx。
C語言是隨着Unix而誕生的語言,在Unix下開發C語言纔是真正的開發。MAC的操作系統是Unix內核的,Windows是沒法裝Unix的,所以要用Linux,Linux是和Unix完全兼容的。
make
make工具內部用的是gcc,用Makefile來統一描述代碼之間的關係。舉例:
#this is makefile 註釋
#源文件:需要鏈接的文件
hello.out:max.o min.o hello.c
#Tab(6個空格) 命令 表示最終編譯出hello.out的命令
gcc max.o min.o hello.c -o hello.o
#找到max.o min.o怎麼來的
max.o:max.c
gcc max.c
min.o:min.c
gcc min.c
然後在當前目錄直接用make命令。makefile的一個優點就是已經編譯生成的.o文件如果沒有改動,下次編譯不會再重新編譯。
main函數
C語言再Linux中運行的時候,是可以與操作系統交互的,
int main(int argv,char* argc[])
{
return 0;
}
Linux的指令gcc main.c -o main.out && ./main.out
表示&&前的指令執行成功後執行後面的指令,那麼怎麼判斷前面的成功了呢?執行完一個命令或者運行完一個程序之後執行指令echo $?
,如果結果是0,說明是正常執行,否則返回錯誤碼,就是main的返回值。例如,如果main中return 101
,gcc之後執行./main.out && ls
,ls指令就不會執行,並且指令echo $?
的結果就是101。
main函數的兩個參數,argv表示命令的參數個數,argc表示參數具體是哪些。例如,./main.out
命令,argv就是1,./main.out -ls
,argv就是2。
輸入,輸出,和錯誤流
重定向
標準的輸入流是0,標準的輸出流是1,不寫默認是輸出流。例如,命令./a.out 1>> a.txt
就表示把標準輸出流從原來的終端重定向到文本文件中,並且>>
表示如果多次重定向,文件中的內容不會覆蓋,而是追加在後面,>
就表示覆蓋文件。
再例如,ls /etc >> etc.txt
就表示把etc目錄下的所有文件名列表存在etc.txt文件中。
管道
Linux中多個小工具如何結合起來使用?Linux中自帶了很多命令,每個命令其實就是一個小程序,比如ls,grep(從文本文檔中查詢包含指定字符的行)。如果想把小工具結合起來用就要用到管道|
,把前一個命令的輸出流作爲第二個命令的輸入流。
例如ls /etc | grep ab
就表示把ls命令的輸出流輸出到grep,ps -e | grep ssh
就表示查看所有進程裏有沒有包含ssh的進程。
管道小例子
現在我們自己編寫小工具,然後通過管道連接在一起。
程序1:求平均值
//avg.c
#include <stdio.h>
int main()
{
int s,n;
scanf("%d,%d",&s,&n);
float v=s/n;
printf("v=%f\n",v);
return 0;
}
然後編譯cc avg.c -o avg.out
。
程序2:統計輸入
//input.c
#include <stdio.h>
int main()
{
int flag=1;
int i,count=0,s=0;
while(flag)
{
scanf("%d",&i);
if(0==i) break;
count++;
s+=i;
}
printf("%d,%d\n",s,count);
return 0;
}
然後編譯cc input.c -o input.out
。
現在把兩個程序結合起來,運行./input.out | ./avg.out
。
Linux C指針與內存
gdb的使用
gdb是gcc自帶的調試工具。常用的指令:
- gcc -g main.c -o main.out 直接gcc出來的程序不可以調試,編譯時加個-g就可以調試
- gdb ./main.out 開始調試
- list/l 列出源代碼
- 按回車表示繼續執行上一個命令(上面的指令如果源代碼沒有完全顯示,按回車可以繼續顯示)
- break 12 斷點
- start 開始單步調試
- p a 打印變量a的值
- n 下一步執行
- s 進入函數裏
- bt 查看函數棧(棧頂部#0是當前所在函數)
- f 1 切換到函數棧1
- x/3d 0x7fffffffde4 顯示內存中連續3個值,按整數輸出,從地址0x7fffffffde4開始
x/6cb 0x7fffffffde4 顯示內存中連續6個值,按字符輸出,按單字節打印,從地址0x7fffffffde4開始
內存管理
計算機中數據最小的單位是字節(Byte),一個字節是8個二進制位(bit),因爲電子計算機是由邏輯電路元件組成的,電流只有兩種狀態,高電位1和低電位0,那麼計算機存儲再複雜的數據也脫離不了1和0 。
我們的計算機中可能會插兩個或者4個內存條,插2個2G的和一個4G的內存條效果是一樣的,計算機會把內存看成一個整體來使用,但也不是隨便插多少內存都可以。32位的計算機最大使用4G內存,因爲32位計算機的地址總線是32位,也就是尋址空間是32位,給內存編號只能編到32個二進制位,地址總線可以存在多種狀態,尋址的時候就是根據不同的狀態尋址。32根總線就可以有232個不同的狀態,一個狀態就可以代表一個內存的最小單位也就是字節,一個地址存1字節數據,一共有232個字節,也就是22·210·210·210B=4GB。
64位計算機最多就可以有264內存,內存地址可以從000…000(64個0)到111…111(64個1)。264這個數值過大,目前市場上都沒有達到這麼大的內存。
內存都是由操作系統管理的,因爲一個計算機中可能要運行多個程序,要程序員來直接管理內存是不太合理的。操作系統除了給內存編號以爲還可以給內存做一定的規劃。分給操作系統的內存和用戶的內存隔開,這樣就算用戶內存佔滿了,操作系統的內存也不會被大量佔用,不會卡住,死機,可以通過操作系統關閉應用程序。
內存空間如下圖所示:
用戶寫的代碼編譯之後存到磁盤,運行的時候,編譯的二進制文件會加載到內存的代碼段,聲明的一些全局變量或者常量,靜態數據,如const int,就會放到數據段;動態分配的數據存在堆區,它的大小並不固定,可動態擴張或縮減;調用的函數信息,局部變量放在棧區。
C語言中是無法直接對內存操作的,操作系統會認爲是非法操作,只有操作系統分配給的內存才能操作。
字符串與數組
看下面的程序:
#include <stdio.h>
int main()
{
char str[]="hello";
char* str2="world";
char str3[10];
scanf("%s",str);
scanf("%s",str2);//x
scanf("%s",str3);
printf("str is %s\n",str);
printf("str2 is %s\n",str2);
printf("str3 is %s\n",str3);
return 0;
}
C語言中,只要告訴一個地址,就可以往裏寫入內容,比如scanf("%s",str)和scanf("%s",str3),但是scanf("%s",str2)會出錯,因爲str2是個指針,指向的字符串"world"是在內存中的代碼段,代碼段的值是不可以改的,堆和棧的值是可以寫入的。
另外一些有趣的實驗:
- 如果在定義str之後,str[3]=’\0’; 那麼打印的時候str只能打印出"hel" ,因爲C語言字符串數組遇到’\0’就認爲結束。但是如果循環逐個輸出,還是能輸入 ‘h’, ‘e’, ‘l’, ‘’, ‘o’。越界打印也不會報錯,之會打印指定地址中的值。
- str沒有定義長度,但定義的"hello"有6個字符(包括’\0’),如果定義之後再用scanf輸入的時候輸入超過6個字符,仍然能寫入,也能全部打印出來,但會溢出到str3中。