提 綱:
- 數據類型,表達式:
- 數據類型長度
- 數據范圍推算
- 隱式類關系轉換
- 短路計算
- 數組與串:
- 地址
- 數組(元素個數一定是常量)
- 字符串與字符數組區別(結果標記)
- 函數與棧幀:
- mov,push,pop,call,ret,leave...
- 反匯編工具 Objdump 使用方法
- 使用匯編指令分析棧幀圖
- 函數參數與壓棧順序
- 值的返回(eax)
- 鏈接:
- c程序產生過程
- ELF文件格式
- c語言程序內存分布圖
- 全局與局部變量
- 鏈接與重定位
- 符號解析規則
- 動態庫
- 預處理:
- #include機制
- 帶參數的宏
- 內聯函數(inline)
- 條件編譯
- makefile:
- 指針:
- 語法
- 指針的指針
- 指針做參數,指針做返回值
- 指針控制移動和間接引用
- void *與null
- 數組指針與行指針
- 指針的數組
- 接口設計規范
- 文件系統(部分待續)
******************************************************************************************************************************************************************************************************
###############################表達式#################################
####數據類型####
內置類型:int short chart double ...
構造類型:結構體 指針 數組 枚舉 函數 ...
空類型:void
int short long 三種整型
4byte 2byte {32bit 4byte;64bit 4byte)
*CPU 指令個數 寄存器指令個數 ————>體系結構
intel amd -->i386 體系結構地址總線:32位
訪問的地址總量:2*2...(32),4G.
在32位上8個字節用long long(long double 16字節)
c89中用long long? gcc a.c -o app -sd=c89;
運算符:size of()字節數求取;
typeof()求變量類型的名字;
####整型變量的范圍####
int-->32bits
在內存中兩種穩態:二進制表示
編碼方式:補碼
0有唯一編碼,計算速度快
32位 第一位;符號位(1負0正);
范圍-2(8n-1)~~2(8n-1)-1;n為字節數,量級int 10(9),short 10(5);
如果超出范圍就溢出了(overfloat);
####浮點數####
float型,作為協處理器集成於cpu中;用移碼表示,常用於科學圖形計算當中,有符號位,后面31位中前M位為數值,后N位為精度;
浮點計算速度慢於整數
中央處理器『協處理器,中央處理單元 內部管理單元(memory management unit)(虛擬地址 物理地址)』之所以在主板上消失了是因為都集成於CPU中了。計算機中處
####char 字符型數據####
占用一個字節,存儲字符對應的ASCII碼;
####表達式####
除法永遠向下去整
ALU(agrithal logical uint)算數邏輯單元 只做加法和移位運算
編程中注意速度避免用除法 乘法可以
####邏輯運算符####
支持短路運算(short circuit)
例子:char *who;
(strcmp(who,"root")!=0)&&(printf("you are not root"));
等同於 if(strcmp(who,"root")!=0){printf("you are not root")};
####隱式類型轉換####
自動統一到下一個類型進行運算 小類型轉換成大類型 保持精度
char-->short-->int -->unsigned int -->long-->double<--float
##########################數組與字符串################################
####數組####
變長數組 malloc int a[n];與編譯器相關其采用默認的N值進行分配 不顯示錯誤;
例子:int a[10]; a[10]不行,結果不可預測,造成數組越界了;
%d: 輸出轉換成十進制有符號整型
%u:無符號整型
%o,%x:用來查看 不用有符號
%s:字符串,%c字符
%p:專門用來打印地址,pointor
####存儲模式####
小端法(little-endian)i386體系:高位存高字節,低位存低字節。
在編程時用虛擬地址,不是物理地址,內存不夠時可以用外存。缺頁異常。
訪問速度:緩存50ns,內存100ns,磁盤100us,移位運算1ns,/,%10ns。
局部性原理:數據最好連續性訪問。
解釋操作系統:分時復用。即不同程序之間相互一直切換。
虛擬的經過MMU進行映射(內核提供算法)物理的,disc與內存進行交換,硬盤中有用的換取內存中無用的(換頁機制,即缺頁異常)。
佐證:訪問占用內存很大的時候,不斷用頁面切換到磁盤中暫存即磁盤與內存的交互。
《微機原理》《數據結構》《深入理解計算機系統》《算法導論》
####串####
字符串:特殊的字符數組;多了一個結束標記“\0”,字符串首地址開始到“\0”之間的內容。例如 五個字符的串是六個字節。
字符串的初始化:char buf[1024]="hello";前5個是hello第六個是\0,以后的也都是“\0”。char buf[5]="hello";是錯誤的 六個字節。
int a[10];
sizeof(a),求取字節數為40。
char buf[]="hello";
sizeof(buf),為6.
sizeof(&buf[0]),地址是32位,即4個字節。
常用函數:輸入一個串 char buf[1024];
scanf("%s",buf);\\輸入一個串到buf中
gets(buf);
puts(buf);\\打印串
strlen 求字符串長度;strcmp 字符串比較;strcpy 字符串復制;strcat 字符串連接;
scanf函數對空格非常敏感(空格與回車等價),這時只能用gets()函數。
gets()函數沒有限制輸入的字符,只傳輸了首地址,會導致stack smash(棧崩潰)。
#############################函數與棧幀###############################
####函數 (子程序)####
返回值 函數名 (參數列表)函數的聲明的話可以只寫參數的類型
形參和實參。
函數名代表底層中代表函數的入口地址(編譯器,入口地址,跳轉),函數的第一層指令的地址。
!!!objdump 反匯編(a.c-->app(二進制可執行程序)--》匯編中的匯編語言)把一個二進制程序反成匯編語言。
-d 反匯編。
-S 列出源(通過反匯編方法了解C)。
-g 為程序生成一個可供指示的符號表。
app>out 就可以用vi編譯器查找Out 中的代碼。
匯編中沒有數據類型的概念,主要有寄存器和內存。
單:指令 操作數;
雙:指令 操作數1,操作數2;
寄存器:eax,b c d:通用寄存器,四個字節的存儲空間,esi,sdi:下標寄存器,數組的下標,不能存負數。esp(棧頂地址),ebp(棧底地址):只能存地址,即指
針寄存器。eip 保存下一條指令地址。跳轉都是eip相關的。
棧,先進后出。
####指令####
push %ebp :寄存器中的值壓到棧里邊去,esp 指向哪里就把值存到哪里。1,將esp向下移動,2,將ebp值保存到esp指向的空間。
pop %ebp :出棧,1,取出棧頂的值,將esp向上移動。
call 地址 :跳轉指令,調用函數(跳到函數入口出執行) push %eip
return :返回指令,取出返回地址 pop %eip 還原到eip中
mov:(雙操作指令) (數,寄存器)(寄存器,寄存器)((寄存器),寄存器(找對應的內存單元的值放到第二個寄存器中));
mov -8(%esp),%eax(%esp的地址減8,把對應內存中的值放入eax中)。
leave:1,mov %ebp,%esp,2,pop %esp。
!!!gcc main.c -o app -g 表示出c。
objdump -dS app>out
vi out
返回值都是通過eax寄存器返回的。
參數的壓棧順序是:從右向左依次入棧。
棧從上向下生長,棧頂esp棧底ebp 上面是棧底,從上向下生長。
!!!GDB
gdb list+行號 刻出代碼
break+行號 設置斷點
print+變量名 打印出值
next 跳轉函數
step 進入函數里邊
si 分條指令進行執行
disassemble 反匯編當前函數 +函數名或地址 反匯編指定的
inforegisters 顯示所有寄存器當前值
start 從程序命令一步一步的執行
quit 退出
echo 打印
函數返回值:沒有RETURN 就得返回上一個程序main的返回值。
段錯誤解釋:返回值是eax 但是不能超過四個字節。
局部變量的產生:移動esp產生值,是隨機的,上一個棧幀殘留的。
全局變量和局部變量:1,作用域上有差別;2,生命期不同,全局變量一直存在,局部變量調用后就沒有了,前者一直存在,后者調用后就沒有了;3,全局變
量存在bss data段,局部變量存儲在棧幀上,即存儲的位置不同。
###########################C程序內存布局##############################
####ELF文件格式內部結構(磁盤中的布局圖)####
ELF header:查看匯總信息頭,保存可執行文件的執行信息
段 .text:代碼段,存儲二進制字節碼
.data:數據段, 存儲全局變量(局部變量放在棧幀里)
rodata:只讀數據段, 存儲字符串常量 “ ”。
bss:塊緩沖段,存儲未初始化的全局變量,beiler save space 節省空間。
用於鏈接 .rel.data:在模塊鏈接數數據的重新定位。
.rel.text:在模塊鏈接時代碼的重新定位。
.symblol:文件內的名字 保存全局符號。
.debug:給gdb用調試信息的。
ELF(tail)
!!!strip -s app 拔去用戶無用的段
objcpy 把ELF中除了四個段以外的全部內容刪掉。
從磁盤中拷貝到內存的過程叫做加載(load)。
!!!ldd app 查看庫
readelf -s 讀取ELF文件中各個段的表象。
##################################鏈接################################
####一個源文件生成最后的可執行程序####
a.c-->1 a.i-->2 a.s-->3 a.o-->4 app(二進制可編程程序)
1,預處理(比如注釋,.c文件轉換成有用的源代碼,加快轉換速度);
2,編譯(翻譯成匯編語言代碼);
3,匯編 目標文件 .o;
4,鏈接 合並的過程,將多個.o文件合成一個可執行程序。
!!!-E 只執行預處理
-S 只進行編譯
-c 只進行匯編
虛擬地址=段首地址(由鏈接腳本決定)+邏輯地址(從0 開始,即偏移)。
偏移不是真值,引用函數的時候需要重新填寫,這個操作成為重定位。WHY???
每一個.c文件對應一個.o,多個.c對應多個.o,(函數的相對位置發生了變化),程序鏈接:函數和全局變量位置發生變化,call調用時地址可能無效,改成鏈接
后的地址再進行重新定位。
多文件編譯:兩個或者多個.o文件合並的過程為鏈接。
!!!file app.o 查看文件
readelf -S app.o 讀取各個表象
objdump -xdS -x 是列出重定位信息
鏈接與加載的信息包含INIT 函數(程序入口 call main函數),在合並.o文件過程中加進來,.text代碼段增加許多。
####符號解析規則####
int a;(聲明)int a=100;(定義 初始化了的a)
1,不允許有多個定義,不允許同一個符號定義多次;(定義成什么值無所謂就是不能重新定義);
2,有一定義多個聲明,選擇使用符號時使定義的符號;(允許一個符號定義,多次聲明可以);
3,多個聲明則選擇其中一個(對於一個變量多次聲明,只有聲明沒有定義是允許的,因為其中一個自動升為定義,但函數不允許,其中必須要定義);
內部局部變量和外部全局變量不沖突,因為作用域不同;函數內部引用全局變量是可以的。
double x;
void f()
{
x=0;
}
#include<stdio.h>
int x = 15132;
int y = 15134;
void f();
int main(int argc,char *argv[])
{
f();
printf("x=0x%x y=0x%x\n",x,y);
}
輸出結果是 0 0 ,可以用重定位的知識解釋,反匯編查看可以知道地址可以回填但是指令並沒有改變,所以x過了4個字節后把y的4個字節都覆蓋了,都是零了,結果就是 0 0。
!!!ldd app 庫名字顯示
libc.so.6 C庫 so (share object)
ld-linux.so.2 動態鏈接器 實現鏈接
####動態庫(共享庫)DLL dynamic linking library####
庫中包含所有函數的實現(二進制字節碼的形式存在);二進制可執行文件(.elf)
so中有幾個ID要存儲,在.elf中多了一張符號表,存在與代碼段和數據段之間,指定一個ID就可以查詢對應的函數入口地址。即動態庫中ID與入口地址是一一對應
的,.so文件擁有的。
標准C庫中有900K。
動態庫的特點:
1,動態鏈接(發生在程序運行的時候)
編譯時並不合在一起而是作標記,添加信息。運行時直接連接內存中有的庫。如果庫出現在內存中,直接加載應用不用加載庫了,如果庫不存在,則先加載庫,
再加載程序,即動態庫的加載一定在程序加載之前。當程序運行時庫和程序之間不斷進行跳轉。
2,共享(如果在程序運行之前庫已經存在,在內存中被多個程序共享使用,節省了空間);
問題:動態庫不是一個使用,在運行之前可能有人已經使用了,每次用時庫加載的地址可能不同,call后面的地址代碼要修改,即肯定不行,這個時候需要子程
序鏈接表(procedure linkage table)
不管加載到什么位置,call都跳轉到一個相應的位置上才可以,缺點是慢。
函數地址變化,但是對應的PLT表地址固定(puts@plt--puts 函數在 plt中的地址)
運行時 GOT表,GOT【2】動態入口地址;GOT【1】動態鏈接器的描述信息;GOT【0】第0個表項。尋早要用的函數給地址,此過程為延遲綁定(懶惰算
法),PLT表項中16個字節清空,放入函數地址,下一次用的時候直接跳轉到表項中獲取函數地址。
!!! gcc main.c -c (匯編 生成.o文件)
gcc main.o -o app
ldd app (產生相關庫信息自動加載的)
objdump -dS app>out (反匯編)
objdump -dS -j .plt app>out -j 只反匯編一個段
objdump -dS -j .got.plt app>>out 追加
編譯vi.
printf 格式化打印;puts()不用格式化打印;
!!!history
ps ax 正在運行的
動態庫用 .so .so.6 6是版本號
####制作動態庫####
-Share 可以動態鏈接的 -fPIC 生成未使用過的代碼,生成id首地址對應的表。
1,gcc add.c -o add.so -Shared -fPIC
readelf -dS add.so 段查詢
自己編寫的程序就可以鏈接動態庫
2,gcc main.c ./add.so -o app -->包含動態庫信息的app
objdump -dS app
更新即版本不同(mv 指令實現)動態庫更新方便 只要接口不變 只需要應用程序暫停再重新鏈接動態庫使用。
自主學習的能力很中要,要有基本概念,例如,現代化工業軟件開發,如見開發和發布的模式接口設計模式等等。add.so(產品),add.h(放在main.c中聲明)
add.h 幫助文檔提供給用戶。.so文件也要打包給用戶。
##############################預處理##################################
####linux下可執行程序都是ELF格式的####
1,文件包含,2,宏定義,3條件編譯。對人有用但是對及其沒用!
1,文件包含;(機制是復制)
#include<stdio.h> 間括號 是系統指定的目錄中搜索文件 /usr/include
#include"stdio.h" 雙引號 當前目錄下搜索需要的頭文件
需要注意的是:頭文件中可以有:1,函數的聲明;2,可以加全局變量的聲明;3,宏定義可以有;4,新的類型定義;5,用include包含其他文件;不能有的
是:變量的定義和函數的定義。
隱式聲明:要是調用一個函數之前應聲明,但是gcc編譯器自動判斷函數類型。
gcc中指定文件包含的路徑,a.c中head.h不再當前目錄下而在include目錄下,則如下命令 gcc a.c -c -Iinclude
2,宏定義,讓程序更簡潔,代碼修改更方便,易於維護,易於理解。
定義一個宏,#define 標識符 字符串 #define G 9.8
注意:宏的定義不作數據檢查,有效范圍是限於文件內部,提前取消宏的話 # undef 宏名
定義一個全局的宏:放入頭文件中,每個.c include頭文件就可以了
帶參數的宏(很像函數 但有區別),#define 宏(參數表) 字符串
宏的參數沒有類型(因為是在預處理時發生的) 宏定義多行時用續行符‘\’
宏的副作用 #define test(a,b) (a)*(b) 因為 test(a+b,a-b)時 可能a+b*a-b 運算順序發生了變化。前面的是正確寫法。
宏與函數的區別:1,宏是編譯時的概念,函數是運行時的概念;2宏在編譯時發生運行速度比較快,函數在運行時發生速度比較慢(壓棧,跳轉等)3,宏不會
對參數類型進行檢查,函數有嚴格的類型檢查;4,宏不會分配存儲單元,函數自己分配存儲單元(函數會產生棧幀,宏不會);5,宏會使代碼體積變大,而函數不會。
6,宏不可以調用本身,函數可以。
inline關鍵字 將函數展開,不進行壓棧跳轉等,在過程中直接將函數展開。
內聯:不跳轉,壓棧等類似於宏,是更好的宏。把代碼給展開不進行跳轉,內聯函數有宏的特性,又彌補了宏的特點,即不進行檢查,在優化后進行展開。
編譯器可以自己指定內聯的函數,inline 可以手動指定內聯函數。
3,條件編譯
#ifdef 標識符 程序段1(如果有標識符,則。。。)
#else 程序段2 (否則。。。)
#endif
有什么用??? --調試輸出(調試開關)只要一個地方作改動,整體都變化。調試開關是一個技巧。
gcc DEBUG.c -o app3 -g -DDEBUG //由編譯器在我們的源代碼中定義某個宏
#ifdef DEBUG
#define DEBUG_PRINT1(arg0,arg1)printf(arg0,arg1);
#else
#define DEBUG_PRINT2(arg0,arg1)printf(arg0,arg1);
#endif
int _strlen(char str[])
{
int i;
for(i=0;str[i]!='0';i++)
DEBUG_PRINT1("%c",str[i]);
return i;
}
################################Makefile################################
####Linux 下的腳本,對代碼的自動識別和編譯####
1,自動編譯(許多.c文件)輸入一個命令就可以自動編譯
2,選擇編譯(實現篩選,自動編譯)
target(產品):prerequest(依賴軟件) 要生成產品就要有很多依賴文件。
commond 命令(怎么樣依賴文件來生成產品)
依賴的文件可以修改但是一定要存在;
命令必須以制表位開頭;
產品 目標 命令 --》規則,Makefile中就是一個接着一個的規則。
依賴文件要新於目標文件,用較新的文件生成更新的目標文件。體現了選擇性編譯,看依賴文件是否新於目標文件時間上能看出,就能生成新的目標。
####多條規則的Makefile####
如果文件和目標文件沒有關系則沒有用,即孤島規則。默認規則目標為最終目標。
執行Makefile的最終目的就是執行最終目標。
make 目標名 手動指定最終目標。
規則中只有目標是必須的,命令與依賴可以省去。
例子:(release 發布程序)
release:a.c
gcc a.c -o release (strip -s release)用一個規則可以執行多個命令。
debug:a.c
gcc a.c -o debug -g DDEBUG
clean: -->命令一定被執行到
rm -f *.o
rm -f app
echo "job done"
clean 是偽目標,不依賴於任何文件,只要能被執行到一定執行命令,用來快速刪除編譯過程中產生的中間產品。
all:app app-r all最終生成的可以沒有命令。
快速生成依賴工具 all:*.c
gcc *.c -o app
make debug 調試程序;make release 發布程序。
################################指針###################################
類型名 *變量名
int *p = &a;
p 直接引用 ;引用p的值 *p 間接引用 ; 取指針變量指向的變量
指針變量也是變量。
int *p,q;//同類型的變量可以寫在一行
int a,b; p=&a;
q=&b;//整型變量不用賦&b的值,會出現警告。
typedef 已有類型 新類型(別名 _t是標准);//把程序中已知類型定義成自己的新類型
typedef int my-int_t;
typedef int * int p_t; int p_t p,q;//p,q都是指針類型,此處體現了與宏定義的不同,要是宏的話,q就不是指針類型了。
直接引用的不用管p中的內容新的內容直接把它覆蓋了。
間接引用 *p=10;將10付給p所指向的空間,可能引起段錯誤,用間接引用的要清楚p所指向的空間是否有效,否則引起段錯誤。
數組是首地址,指針也是地址,則*p《=》p[0]
1, char *p="hello";//p指向的空間是只讀數據段
*p=‘h’;//不可以,因為只讀數據段不能修改,出現段錯誤
2, char p[]="hello";
*p='H';//結果是首字母大寫,*p<=>p[0](都是首地址),hello串從rodata段復制到了棧中局部數組中,p[]為局部數組初始成“hello\0”,等價於char
p[6];strcpy(p,"hello");
3, char *p;//空有一個指針P。
strcpy(p,"hello"); //p指向的空間不一定是有效的可以訪問的空間,所以“hello”不能被復制到p指向的空間當中。
修改:char buf[10];
char *p;p=buf;
內部的char strcpy(char *p, char *q){
while(*q!='\0'){
*p=*q;p++;q++;
}*p='\0';return p;
}
間接引用指針變量時候必須保證變量指向的空間有效即可以訪問。
####指針的指針####
int **p;//p是個指針變量,保存了指針變量的地址。
int **p,*q;
int a;a=100;
q=&a;p=&q;//保存了指針變量的地址,兩個*就是極限了
int **p,*q;
int a;a=100;
p=&q;*p=&a;//*p=q,q=&a,->*p=&a;必須保證p指向的空間有效,指針變量也有地址,即加上p=&q.等價於a的首地址放入到q中,q=&a。
####指針變量作函數的參數####
void swap(int a,int b)
{
int t;
t=a;a=b;b=t;
}
int main(void)
{
int a=1,b=2;
swap(a,b);
return 0;
}
//結果還是1,2.沒有實現交換的功能,副本改變了,但是本體並沒有改變。
改正:swap(int *a,int *b);t=*a;*a=*b;*b=t;swap(&a,&b);
指針指向變量類型,意義在於,1,控制間接引用;2,控制指針移動
隱式類型轉換:強制--》主要用在指針上。強制類型轉換,(新類型)表達式;int *p;(char *)p;
指針做到了數據類型還原成字節,就像匯編一樣。編寫C,低於9K是不行的。
void memcpy(void *d, void *s,int n)//轉換n個字節從s到d,void *表示泛型指針,即任意類型指針。void *p;*p;不能這么用,p指向的類型不定,所以取的字節數不確定。
{
char *s1, *s2;
int i;
s1=(char *)d;
s2=(char *)s;
for(i=0;i<n;i++)
{
*s1 = *s2;
s1++;s2++;
}
}
編寫一個程序判斷大小端:
int test_endian(void)
{
int a=0x12345678;
return 0x78==*((char *)&a);
}//返回1小端,0則大端。(char *)一個字節,78;(short *),兩個字節,5678;
####指針指向指針變量類型典型用法:控制指針移動####
int a;
int p=&a;
char *p="hello";
p++;//p跳過的不是一個字節,而是p+i*sizeof(type) 個字節,p++跳過的是p指向的變量的字節數。
####NULL:空指針(相當於宏) #define NULL (void *)0####
0號地址單元,永遠不能訪問。清零作用,char *p=NULL;預處理之后等價於,char *p=(void *)0;char *p=NULL;*P=‘A’;會出現段錯誤,因為在間接引用之前沒
有保證p所指向的空間是有效的
三種段錯誤:1,char *p="hello";*p='h';2,char *p;strcpy(p,"hello");3,int **p;int a;*p=&a;
####指針作參數####
間接引用時可以對主函數影響,例如swap()函數;直接引用作參數時對主函數沒有作用;void fan(char *p){p=NULL}int main(void){char *p=0x200;fac(p);return 0;}
結果還是0x200。
####指針作返回值:不能返回局部變量的首地址。int *f(void){int a=100;return &a;}不行,&a是無效的,稱之為野指針。
####指針指向變量的類型:1,控制指針移動;2,控制間接引用。
####泛型指針:1,不能間接引用;2,不能進行移動;3,NULL 4字節;
####typedef與宏的區別:加一個引號。
C中三種0常量:1,0:4bytes,2,'\0':1byte,3,NULL,4bytes;
####數組和指針的關系
int a[10];int *p;p=a;/p=&a[0];
數組名實際上和地址是等效的。
1.for(i=0;i<10;i++)
a[i]=i;
2.for(i=0;i<10;i++)
*p++=i;
3.for(i=0;i<10;i++)
p[i]=i;
4.for(i=0;i<10;i++)
*(a+i)=i;
匯編中,a[2]《=》*(a+2) 編譯速度提高了但是沒有必要。
*(a+i)=a[i]--*a=a[0];
(a+i)=&a[i]--a=&a[0];
調用函數時把數組的首地址作為參數傳遞進來;
實參是地址,即可以用指針代替;
不同之處:數組名是地址常量,指針是變量;
int a[10];
int *p;
p=a;(a=p是錯誤的)
*p++;(*a++是錯誤的)
####數組的指針#### (指向一個數組)
int (*p)[5];//定義一個指向數組的指針,括號是不能省的,p是變量名。
int a[5];p=&a;(p=a是不行的),數組整體看成一個對象給p,&符號不是說取數組地址,是告訴編譯器將a當作整體來看待,並不會產生額外地址,&a是數組a的首
地址。
void fac(int a[])-->等價於 int *a{return a[0];} return sizeof(a);恆為4,調用函數時把數組的首地址作為參數傳遞進來。
int main(void){int a[]={1,2,3,4,5};f(a);} sizeof(a)--20bytes.因為實參為地址,所以可以用指針代替。void(char *a)
void bubble_sort(int a[],int n) 因為內部求不出元素的個數,需要n來定義元素的個數。體現了接口設計的思想(科學簡潔,擴展性強)。
如何設計接口,要科學,與別人的程序對接,要掌握軟件開發的基本流程,模塊化的設計思想。
####指針的使用在c中很靈活####
int a[]={1,2,3,4,5}
printf("%d,%d",*(a+1),*(((int *)(&a+1))-1)); &a+1;//跳轉的是整個數組,強制轉換成int *指針跳轉4bytes,-1之后,跳轉到 5上。!!!!!!!!!
####行指針####
指向一行的指針。
1,已知 int a[10];-->int * ; int a[2][3];-->int ** 不成立。
2,已知 T a[]; --> T *;
3,已知二維數組可以看成是一維的,每個元素是一行,int a[2][3];-->行型 a[2]; 行 *p;--》數組指針。a [2][3];<--> int (*p)[3];*p是行指針,3是3個元素,a+1 是第一
行。
####接口設計####
函數中要返回副本,不要把原文件給別人,一般是返回值。
傳出參數:1,必須是指針;2,為了傳出返回值而存在的;
例子:strcpy(s1,s2);s1就是傳出參數。
typedef struct{int max;int pos;} res_t;
void get_max(int a[],int n,res_t *r);//接口的設計,結構體中可以修改,但是接口不變,做到了兼容,每次函數升級,接口並不用變。
int get_max(int a[],int n,int *pos)
{
int max, i, p=0;
max=a[0];
for(i=0;i<n;i++)
{
if(a[i]>max)
max=a[i];p=i;
*pos=p;
return max;
}
}
int main(void)
{
int a[]={1,2,3,4,5};
int m, p;
m=get_max(a, 5, &p);
printf("max: %d at: %d",m,p);
return 0;
}
pos:是傳出參數,value-out.
傳入參數:函數內部作數值用,不作改變,與傳出參數對應,例如:strlen(char *s);char *s 就是傳入參數,調用之前有用,函數的過程中不改變
傳入傳出參數:冒泡函數 void bubble_sort(int *n,int n);int *n是傳入傳出參數。
接口要有容錯能力,容錯方案解決:
void get_max(int a[],int n,res_t *r)
{
static res_t bakap;
if(r=NULL) r=&bakap;
return r;
}
正常緩沖區的話返回自己的,如果是null 提供緩沖區bakap供返回
char * (返回的是字符型的指針) get_common(char *s1,char *s2,char *out,int size) s1,s2是傳入參數,out是傳出參數,可能是有效或者無效的空間,考慮到安全
性,要有size ,這是完整的接口設計。
操作系統--》管理者,底層實現者。linux 0成功;-1失敗。
系統調用,是一個函數,是操作系統的一部分,內核函數,系統調用特權級。自己用的是用戶級。API 用戶編程接口,上層是應用層,下層是系統層。
####malloc#### 動態分配內存塊(程序運行的進程中)
程序編寫時空間大小不確定,要用動態分配函數malloc,動態分配內存。
size_t 無符號長整型
void *malloc(size_t size) size 動態分配的字節個數
void *動態分配首地址作為返回值(從首地址開始向后的size個字節都是可以使用的),成功的話返回,錯誤的話就返回NULL(ENOMEM 沒有內存)
malloc函數系統調由sbark負責向系統申請內存。
int main(void)
{
int n, i;
int *a;
scanf("%d",&n);
a=(int *)malloc(sizeof(int)*n);//動態申請4n個字節。
for(i=0;i<n;i++)
scanf("%d",&a[i]);
for(i=0;i<n;i++)
printf("%d\n",a[i]);
free(a);
}
void free(void *ptr)//釋放內存的首地址,沒有返回值。
free(a);//有申請就要有釋放。
系統調用浪費時間,要盡量避免,sbark向系統內核作系統調用。
堆(heap)中存malloc動態申請的內存,堆是只升不落的,malloc機制是只申請不退還。
typedef struct{int size:29,pom:3(位域,加快內存塊的權限)}mem_t 是管理結構
29+3 32位,前29位內存塊的大小,后3位權限(前兩位是讀寫可執行權限,后一位判斷內存塊1忙0閑)????????????
#############################文件操作#################################
FILE *fopen(const char *path,const char *mode)
//結構體,用來引用打開的文件。path 路徑。mode 權限, 文件模式。
w:只寫打開文件,文件如果存在的話將自動截到0,即源文件扔掉,自動創建。
w+:讀寫,如果不存在文件自動創建,否則自動截短到0,原文件扔掉。
r+:更傾向於讀,沒有w+的功能,正常讀寫。
懸掛:a:寫入的內容自動追加到結尾,a+:寫文件尾,沒有的話自動創建,存在的話寫在結尾。
返回結構體文件指針,如果失敗返回NULL。
讀方式打開目錄可以,寫不可以。
#include<stdio.h>
int main(void)
{
FILE *fp;
fp=fopen("test","r");
if(fp==NULL)
perror("error occurs ...");//參數自己定義的字符串,把錯誤的原因接在串后邊
}
!!!ls -l test
chmod u-x text
三種錯誤:1,沒有文件;2,權限不夠;3,打開目錄(只寫)。
int fclose(FILE *fp) //要關閉的文件的指針,用來關閉文件成功返回0,失敗返回-1.
如果文件不關閉,長時間的話就打不開了。
####以字節為單位的讀####
int fgetc(FILE *stream) 文件結構類型(引用打開文件) 讀一個字節作返回值返回(拓展成 int)。
EOF :文件結束標記,END OF FILE
char ch;
while(1){
ch=fgetc(fp);
if(ch==EOF) break;
fputc(int c,FILE *stream) //輸出一個字節,int中前三個丟掉,最后一個保留。
####行為單位的讀取####
char *fgets(char *s,(用來保存讀取的內容)int size,FILE *steam) size 為最多讀取的size-1個字節。
從F中讀取到s中,size 緩沖區的大小,體現了接口設計的安全性。
vi編譯器會自動檢測有沒有換行,讀到換行‘/’ 時結束。
int fputs(const *s,FILE *steam)
成功返回0,失敗返回-1.
s指向的空間必須是串,以‘\0’結束。比較長的文件多次讀完。
塊I/O,fread fwrite
size_t(讀取的對象個數) fread(void *str,size_t size,(對象字節數),size_t n,(讀取n個) FILE *steam)
size_t(返回實際輸出的對象個數)fwrite(char *str,(待輸出的內容)size_t size,size_t n,FILE *steam(向哪個文件輸出))
int main(void)
{
FILE *fp,*out;
int n,i;
char buf[5];
fp=fopen("test","r");
out=fopen("out","w");
fread(buf,sizeof(char),5,fp);
fwrite(buf,sizeof(char),5,out);
fclose(fp);
fclose(out);
return 0;
}
操作系統的特點:長期運行, 高權級;
操作系統提供給我們底層的抽象,應用層使用接口就行了,系統調用就是這個接口,利用它就可以操控底層的資源了。
系統調用操作內核數據結構,操作底層硬件資源,庫函數封裝了系統調用,系統調用實現庫函數功能。
*****************************************************************************************************************************************************************************************************