Linux下C語言編程快速提煉====基礎篇


提 綱:

  1. 數據類型,表達式:
    • 數據類型長度
    • 數據范圍推算
    • 隱式類關系轉換
    • 短路計算
  2. 數組與串:
    • 地址
    • 數組(元素個數一定是常量)
    • 字符串與字符數組區別(結果標記)
  3. 函數與棧幀:
    • mov,push,pop,call,ret,leave...
    • 反匯編工具 Objdump 使用方法
    • 使用匯編指令分析棧幀圖
    • 函數參數與壓棧順序
    • 值的返回(eax)
  4. 鏈接:
    • c程序產生過程
    • ELF文件格式
    • c語言程序內存分布圖
    • 全局與局部變量
    • 鏈接與重定位
    • 符號解析規則
    • 動態庫
  5. 預處理:
    • #include機制
    • 帶參數的宏
    • 內聯函數(inline)
    • 條件編譯
  6. makefile:
  7. 指針:
    • 語法
    • 指針的指針
    • 指針做參數,指針做返回值
    • 指針控制移動和間接引用
    • void *與null
    • 數組指針與行指針
    • 指針的數組
    • 接口設計規范
  8. 文件系統(部分待續)

******************************************************************************************************************************************************************************************************

    ###############################表達式#################################
    ####數據類型####

    內置類型: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;
    }

    操作系統的特點:長期運行, 高權級;

    操作系統提供給我們底層的抽象,應用層使用接口就行了,系統調用就是這個接口,利用它就可以操控底層的資源了。

    系統調用操作內核數據結構,操作底層硬件資源,庫函數封裝了系統調用,系統調用實現庫函數功能。

 *****************************************************************************************************************************************************************************************************


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM