Linux C基本原理


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自帶的調試工具。常用的指令:

  1. gcc -g main.c -o main.out 直接gcc出來的程序不可以調試,編譯時加個-g就可以調試
  2. gdb ./main.out 開始調試
  3. list/l 列出源代碼
  4. 按回車表示繼續執行上一個命令(上面的指令如果源代碼沒有完全顯示,按回車可以繼續顯示)
  5. break 12 斷點
  6. start 開始單步調試
  7. p a 打印變量a的值
  8. n 下一步執行
  9. s 進入函數裏
  10. bt 查看函數棧(棧頂部#0是當前所在函數)
  11. f 1 切換到函數棧1
  12. 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"是在內存中的代碼段,代碼段的值是不可以改的,堆和棧的值是可以寫入的。

另外一些有趣的實驗:

  1. 如果在定義str之後,str[3]=’\0’; 那麼打印的時候str只能打印出"hel" ,因爲C語言字符串數組遇到’\0’就認爲結束。但是如果循環逐個輸出,還是能輸入 ‘h’, ‘e’, ‘l’, ‘’, ‘o’。越界打印也不會報錯,之會打印指定地址中的值。
  2. str沒有定義長度,但定義的"hello"有6個字符(包括’\0’),如果定義之後再用scanf輸入的時候輸入超過6個字符,仍然能寫入,也能全部打印出來,但會溢出到str3中。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章