日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]

發(fā)布時(shí)間:2025/6/17 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ] 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

相關(guān)文章鏈接 :
1.【嵌入式開發(fā)】C語言 指針數(shù)組 多維數(shù)組
2.【嵌入式開發(fā)】C語言 命令行參數(shù) 函數(shù)指針 gdb調(diào)試
3.【嵌入式開發(fā)】C語言 結(jié)構(gòu)體相關(guān) 的 函數(shù) 指針 數(shù)組
4.【嵌入式開發(fā)】gcc 學(xué)習(xí)筆記(一) - 編譯C程序 及 編譯過程
5.【C語言】 C 語言 關(guān)鍵字分析 ( 屬性關(guān)鍵字 | 常量關(guān)鍵字 | 結(jié)構(gòu)體關(guān)鍵字 | 聯(lián)合體關(guān)鍵字 | 枚舉關(guān)鍵字 | 命名關(guān)鍵字 | 雜項(xiàng)關(guān)鍵字)
6.【C 語言】編譯過程 分析 ( 預(yù)處理 | 編譯 | 匯編 | 鏈接 | 宏定義 | 條件編譯 | 編譯器指示字 )
7.【C 語言】指針 與 數(shù)組 ( 指針 | 數(shù)組 | 指針運(yùn)算 | 數(shù)組訪問方式 | 字符串 | 指針數(shù)組 | 數(shù)組指針 | 多維數(shù)組 | 多維指針 | 數(shù)組參數(shù) | 函數(shù)指針 | 復(fù)雜指針解讀)


  • 一. 函數(shù)本質(zhì)
    • 1. 函數(shù)意義
      • (1) 函數(shù)來源
      • (2) 模塊化程序設(shè)計(jì)
    • 2. 面向過程的程序設(shè)計(jì)
      • (1) 程序結(jié)構(gòu)
    • 3. 函數(shù)的聲明和定義
      • (1) 聲明 和 定義 的區(qū)別
      • (2) 代碼示例 ( 函數(shù) 聲明 和 定義區(qū)別 )
  • 二. 參數(shù) 可變參數(shù) 順序點(diǎn) 類型缺省認(rèn)定
    • 1. 函數(shù)參數(shù)
      • (1) 參數(shù)分析
      • (2) 代碼示例 ( 函數(shù)參數(shù) 求值順序 )
    • 2. 程序中的順序點(diǎn)
      • (1) 順序點(diǎn)簡介
    • 3. C 語言 函數(shù) 的 缺省認(rèn)定
      • (n) 標(biāo)題3
    • 4.可變參數(shù) 的 定義 和 使用
      • (1) 簡介
      • (2) 代碼示例 ( 定義 使用 可變參數(shù) )
  • 三. 函數(shù) 與 宏
    • 1. 函數(shù) 與 宏 對比案例
      • (1) 函數(shù) 和 宏 的案例
    • 2. 函數(shù) 和 宏 的分析
      • (1) 函數(shù) 和 宏 分析
    • 3. 函數(shù) 與 宏 的 利弊
      • (1) 宏 優(yōu)勢 和 弊端
      • (2) 函數(shù) 的 優(yōu)勢 和 弊端
      • (3) 宏的無可替代性
    • 4. 總結(jié)
      • (1) 宏 定義 和 函數(shù) 總結(jié)
  • 四. 函數(shù)的調(diào)用約定
    • 1. 函數(shù)的活動記錄 分析
      • (1) 函數(shù)的活動記錄
    • 2. 函數(shù)的調(diào)用約定概述
      • (1) 參數(shù)入棧 問題描述
      • (2) 參數(shù)傳遞順序的調(diào)用約定
  • 五. 函數(shù)設(shè)計(jì)技巧







一. 函數(shù)本質(zhì)




1. 函數(shù)意義


(1) 函數(shù)來源


C 程序結(jié)構(gòu) 由 數(shù)據(jù) 和 函數(shù) 組成;

函數(shù)是由匯編跳轉(zhuǎn)發(fā)展而來的 :

  • 1.匯編操作 : 匯編語言中由一系列的指令組成, 這些指令從上到下順序執(zhí)行,
  • 2.跳轉(zhuǎn)操作 : 匯編中需要做分支循環(huán)操作的時(shí)候, 就是使用跳轉(zhuǎn)指令;
  • 3.指令代碼模塊 : 在匯編中有一組指令代碼, 總是需要執(zhí)行這一組代碼, 需要時(shí)跳轉(zhuǎn)到該代碼處執(zhí)行, 執(zhí)行完畢后在跳轉(zhuǎn)回去, 這就是一個(gè)函數(shù)的雛形;
  • 4.發(fā)展 : 跳轉(zhuǎn)過來 和 跳轉(zhuǎn)回去 相當(dāng)于函數(shù)的 入棧 和 出棧;


(2) 模塊化程序設(shè)計(jì)


模塊化程序設(shè)計(jì) :

  • 1.思想 : 復(fù)雜問題拆解, 將一個(gè)復(fù)雜問題拆解成一個(gè)個(gè)的簡單問題, 這些簡單問題就可以作為一個(gè)個(gè)的函數(shù)來編寫;
  • 2.C語言程序 : 將一個(gè)復(fù)雜的程序拆解成一個(gè)個(gè)模塊 和 庫函數(shù);

一個(gè)復(fù)雜的 C 語言程序有幾十上百萬行代碼, 這些代碼可以分解成若干模塊來實(shí)現(xiàn), 即分解成一個(gè)個(gè)的函數(shù)來實(shí)現(xiàn).





2. 面向過程的程序設(shè)計(jì)


(1) 程序結(jié)構(gòu)


面向過程程序設(shè)計(jì)思想 :

  • 1.中心 : 整體的設(shè)計(jì) 以 過程 為中心;
  • 2.問題分解 : 將復(fù)雜問題分解為若干容易解決的問題;
  • 3.函數(shù)體現(xiàn) : 面向過程 的思想在 C 語言 中的核心就是 函數(shù);
  • 4.分解函數(shù) : 復(fù)雜問題 分解后的過程可以分為一個(gè)個(gè)函數(shù)一步步實(shí)現(xiàn);




3. 函數(shù)的聲明和定義


(1) 聲明 和 定義 的區(qū)別


聲明和定義的區(qū)別 :

  • 1.聲明 : 程序中 聲明 只是告訴編譯器 某個(gè) 實(shí)體 存在, 這個(gè)實(shí)體可以是 變量 或者 函數(shù) 等;
  • 2.定義 : 程序中定義 指的就是 某個(gè)實(shí)體 ( 函數(shù) 或 變量 ) 的實(shí)際意義;

在 test_1.c 中定義變量 int i = 10; 這是定義了 int 類型的變量, 需要為該變量分配內(nèi)存空間;
在 test_2.c 中聲明變量 extern int i; 這是聲明了 int 類型的變量, 變量定義在了別的文件中, 不必為該變量分配內(nèi)存空間;



(2) 代碼示例 ( 函數(shù) 聲明 和 定義區(qū)別 )


代碼示例 :

  • 1.代碼 test_1.c :
#include <stdio.h>//聲明 : 聲明外部變量, 該值是在其它文件中定義的 extern int global_int;//聲明 : 聲明函數(shù) plus, 該函數(shù)定義在下面 int plus(int i, int j);int main() {//聲明 : 聲明函數(shù) square, 如果不聲明編譯時(shí)會報(bào)錯(cuò), 該聲明只在 main 函數(shù)中有效果, 在main函數(shù)之外使用該方法就會報(bào)錯(cuò)int square(int i);//使用函數(shù) square, 如果沒有聲明, 編譯會報(bào)錯(cuò)global_int = 3;printf("%d\n", square(global_int));//使用函數(shù) plus, 如果沒有聲明編譯會報(bào)錯(cuò)printf("%d\n", plus(1, 2));return 0; }//定義 : 定義函數(shù) plus int plus(int i, int j) {return i + j; }//定義 : 定義函數(shù) square int square(int i) {return i * i; }
  • 2.代碼test_2.c :
//定義 : 定義變量, 在這里需要為變量分配內(nèi)存空間 int global_int;
  • 3.編譯運(yùn)行結(jié)果 :






二. 參數(shù) 可變參數(shù) 順序點(diǎn) 類型缺省認(rèn)定




1. 函數(shù)參數(shù)


(1) 參數(shù)分析


函數(shù)參數(shù)分析 :

  • 1.本質(zhì) : 函數(shù)參數(shù)的本質(zhì) 與 局部變量 基本相同, 這兩種數(shù)據(jù)都存放在棧空間中 ( 中間隔著 返回地址 寄存器 EBP 數(shù)據(jù) ) 詳情參考上一篇博客內(nèi)存管理 ;
  • 2.參數(shù)值 : 函數(shù)調(diào)用的 初始值 是 函數(shù)調(diào)用時(shí)的實(shí)參值 ;

函數(shù)參數(shù)的求值順序 (盲點(diǎn)) :

  • 1.實(shí)現(xiàn) : 函數(shù)參數(shù)的求值順序 依賴 編譯器的實(shí)現(xiàn);
  • 2.操作數(shù)順序沒有在規(guī)范中 : C 語言規(guī)范中沒有規(guī)定函數(shù)參數(shù)必須從左到右進(jìn)行計(jì)算賦值;
  • 3.運(yùn)算符編程注意點(diǎn) : C語言中大多數(shù)的運(yùn)算符的操作數(shù)求值順序也是不固定的, 依賴于編譯器的實(shí)現(xiàn);
  • 4.示例 : 如 int ret = fun1() * fun2(); fun1 和 fun2 函數(shù)哪個(gè)先執(zhí)行, 哪個(gè)后執(zhí)行 不一定;

編程時(shí)盡量不要編寫的代碼依賴于操作數(shù)的實(shí)現(xiàn)順序;



(2) 代碼示例 ( 函數(shù)參數(shù) 求值順序 )


代碼示例 :

  • 1.代碼 :
#include <stdio.h>int fun(int i, int j) {printf("%d, %d\n", i, j); }int main() {int m = 1;fun(m, m ++);printf("%d\n", i);/*打印出來的結(jié)果是 2, 1 \n 2分析 : 函數(shù)的參數(shù)的求值順序 不是 從左到右的, 是不固定的這個(gè)順序是編譯器制定的, 不同編譯器該順序不同*/return 0; }
  • 2.編譯運(yùn)行結(jié)果 :

分析 :
函數(shù)參數(shù)計(jì)算說明 : fun(m, m ++); 進(jìn)入函數(shù)體之前先計(jì)算 m 和 m++ 的值, m 和 m++ 是實(shí)參, 在計(jì)算完成之后才賦值給 i 和 j 形參;
順序點(diǎn) : 在進(jìn)入函數(shù)體前是一個(gè)順序點(diǎn), 需要將計(jì)算完畢的實(shí)參 賦值給形參;
實(shí)參 m 賦值 : 賦值給 形參 i, 此處已經(jīng)到達(dá)順序點(diǎn), m 自增操作已經(jīng)反映到內(nèi)存中, 因此 從 內(nèi)存中獲取的 i 的值是 2;
實(shí)參 m++ 賦值 : 賦值給 形參 j, m++ 表達(dá)式的計(jì)算結(jié)果是 1, 因此 j 的值是1;





2. 程序中的順序點(diǎn)


(1) 順序點(diǎn)簡介


順序點(diǎn)介紹 :

  • 1.順序點(diǎn)位置 : 順序點(diǎn)存在于程序之中;
  • 2.順序點(diǎn)定義 : 順序點(diǎn)是 代碼 執(zhí)行過程中, 修改變量值 的 最晚時(shí)刻 ;
  • 3.順序點(diǎn)操作 : 程序運(yùn)行到順序點(diǎn)時(shí), 之前的代碼操作 都要反映到后續(xù)訪問中 ;

順序點(diǎn)列舉 :

  • 1.表達(dá)式結(jié)束 : 每個(gè)表達(dá)式結(jié)束都是順序點(diǎn), 以分號 “;” 結(jié)尾, 每個(gè)分號的位置都是順序點(diǎn);
  • 2.某些表達(dá)式的運(yùn)算對象計(jì)算 : &&, || (邏輯運(yùn)算), ? :(三目運(yùn)算符), 逗號 表達(dá)式 中每個(gè) 運(yùn)算對象計(jì)算后 是順序點(diǎn);
  • 3.函數(shù)運(yùn)行前 : 函數(shù)調(diào)用并且在執(zhí)行函數(shù)體之前, 所有實(shí)際參數(shù)求值完之后是一個(gè)順序點(diǎn), 如參數(shù)是表達(dá)式, 需要將表達(dá)式計(jì)算出結(jié)果;

順序點(diǎn)代碼示例 :

#include <stdio.h>int fun(int i, int j) {printf("%d, %d\n", i, j); }//注意 : 這個(gè)知識點(diǎn)可能過時(shí), k = k++ + k++; 在 Ubuntu 中執(zhí)行結(jié)果是 5int main() {//順序點(diǎn) : 在 k = 2; 表達(dá)式以分號結(jié)束, 這是一個(gè)順序點(diǎn)int k = 2;int a = 1;/*順序點(diǎn) : 分號結(jié)尾處是順序點(diǎn), 該順序點(diǎn)第 1 個(gè) k++, 計(jì)算時(shí) k 先是 2, 自增操作到順序點(diǎn)時(shí)執(zhí)行; 第 2 個(gè) k++, 計(jì)算時(shí) k 還是 2, 自增操作到順序點(diǎn)時(shí)執(zhí)行;加法計(jì)算完畢后 k 變成 4, 兩次自增后變?yōu)?6*/k = k++ + k++;printf("k = %d\n", k);/*a-- && a 進(jìn)行邏輯運(yùn)算, 其中 && 是順序點(diǎn), a-- 在 && 時(shí)執(zhí)行 自減操作, 然后 a-- 結(jié)果變成了 0, a 的值也變成了 0, 進(jìn)行邏輯與操作結(jié)果為 0 */printf("a--&&a = %d\n",a--&&a);return 0; }




3. C 語言 函數(shù) 的 缺省認(rèn)定


(n) 標(biāo)題3


函數(shù)缺省認(rèn)定簡介 :

  • 1.描述 : C 語言中 默認(rèn) 沒有類型的 參數(shù) 和 返回值 為 int 類型;
  • 2.舉例 :
fun(i) {return i }

等價(jià)于

int fun(int i) {return i; }
  • 3.代碼示例 :
#include <stdio.h>//函數(shù)缺省認(rèn)定 : 沒有類型的 參數(shù) 和 返回值 為 int 類型 fun(i, j) {return i + j; }int main() {printf("fun(i, j) = %d\n",fun(3, 3));return 0; }





4.可變參數(shù) 的 定義 和 使用


(1) 簡介


可變參數(shù)簡介 :

  • 1.描述 : 函數(shù)可以接收的參數(shù)個(gè)數(shù)是不定的, 根據(jù)調(diào)用的需求決定有幾個(gè)參數(shù);
  • 2.依賴頭文件 : 如果要使用可變參數(shù), 需要導(dǎo)入 stdarg.h 頭文件;
  • 3.核心用法 : va_list, va_start, va_end, va_arg 配合使用, 訪問可變參數(shù)值;

可變參數(shù)示例 :

  • 1.函數(shù)名相同, 參數(shù)個(gè)數(shù)不同 : open 函數(shù), 有兩種用法, 一個(gè)有 2 個(gè)參數(shù) int open(const char *pathname, int flags) , 一個(gè)有三個(gè)參數(shù) int open(const char *pathname, int flags, mode_t mode) , C 語言中明顯沒有重載, 這里是用可變參數(shù)來實(shí)現(xiàn)的 ; 使用 man 2 open 命令查看 open 函數(shù)的文檔;

可變參數(shù)的注意點(diǎn) :

  • 1.取值必須順序進(jìn)行 : 讀取可變參數(shù)的值時(shí), 必須從頭到尾按照前后順序讀取, 不能跳過任何一個(gè)參數(shù);
  • 2.必須確定1個(gè)參數(shù) : 參數(shù)列表中必須有一個(gè)命名確定的參數(shù);
  • 3.可變參數(shù)數(shù)量無法確定 : 使用 va_arg 獲取 va_list 中的值時(shí), 無法判斷實(shí)際有多少個(gè)參數(shù);
  • 4.可變參數(shù)類型無法確定 : 使用 va_arg 獲取 va_list 中的值時(shí), 無法判斷某個(gè)參數(shù)是什么類型的;

依次讀取可變參數(shù)時(shí), 注意 可變參數(shù) 的 數(shù)量 和 類型, 每個(gè)位置的參數(shù) 是 什么類型, 一定不要讀取錯(cuò)誤, 否則會產(chǎn)生不可預(yù)測的后果;



(2) 代碼示例 ( 定義 使用 可變參數(shù) )


代碼示例 :

  • 1.代碼 :
#include <stdio.h> #include <stdarg.h>/*定義可變參數(shù) : ① 列出第一個(gè)參數(shù) int a② 使用 ... 表明后面有 個(gè)數(shù)不定 并且 類型不定 的 參數(shù) */ double avg(int arg_count, ...) {va_list args;int i = 0; //循環(huán)控制變量double sum = 0; //統(tǒng)計(jì)參數(shù)之和//初始化列表, 讓列表準(zhǔn)備取值va_start(args, arg_count);for(i = 0; i < arg_count; i ++){//從可變參數(shù)列表中獲取數(shù)據(jù), 數(shù)據(jù)類型是 int 類型sum = sum + va_arg(args, int);}//結(jié)束使用可變參數(shù)列表va_end(args);return sum / arg_count; }int main() {//使用定義了可變參數(shù)的函數(shù), 傳入 11 個(gè)參數(shù)printf("%f\n", avg(10, 1, 2, 3, 4, 5 , 6, 7, 8, 9, 10));//使用定義了可變參數(shù)的函數(shù), 傳入 4 個(gè)參數(shù)printf("%f\n", avg(3, 444, 555, 666));return 0; }
  • 2.編譯運(yùn)行結(jié)果 :






三. 函數(shù) 與 宏




1. 函數(shù) 與 宏 對比案例


(1) 函數(shù) 和 宏 的案例


代碼示例 : 分別使用 函數(shù) 和 宏 將數(shù)組數(shù)據(jù)清零;

  • 1.代碼 :
#include <stdio.h>/*定義宏 : 這個(gè)宏的作用是將 p 目前是 void* 類型, 轉(zhuǎn)為 char* 類型, 將后將每個(gè)字節(jié)的內(nèi)容都設(shè)置為 0 */ #define RESET(p, len) while(len > 0) ((char*)p)[--len] = 0;/*定義函數(shù) : 也是將 p 指向的 len 字節(jié)的內(nèi)存置空 */ void reset(void* p, int len) {while(len > 0){((char*)p)[--len] = 0;} }int main() {//1. 定義兩個(gè)數(shù)組, 用函數(shù) 和 宏 不同的方式重置數(shù)據(jù)int array1[] = {1, 2, 3};int array2[] = {4, 5, 6, 7};//2. 獲取兩個(gè)數(shù)組大小int len1 = sizeof(array1);int len2 = sizeof(array2);//3. 定義循環(huán)控制變量int i = 0;//4. 打印兩個(gè)數(shù)組處理前的數(shù)據(jù)printf("打印array1 : \n");for( i = 0; i < 3; i ++){printf("array1[%d] = %d \n", i, array1[i]);}printf("打印array2 : \n");for( i = 0; i < 4; i ++){printf("array2[%d] = %d \n", i, array2[i]);}//5. 使用宏的方式處理數(shù)組1RESET(array1, len1);//6. 使用函數(shù)的方式處理數(shù)組2reset(array2, len2);//7. 打印處理后的數(shù)組printf("打印處理后的array1 : \n");for( i = 0; i < 3; i ++){printf("array1[%d] = %d \n", i, array1[i]);}printf("打印處理后的array2 : \n");for( i = 0; i < 4; i ++){printf("array2[%d] = %d \n", i, array2[i]);}return 0; }
  • 2.編譯運(yùn)行結(jié)果 :

雖然看起來 函數(shù) 和 宏實(shí)現(xiàn)了相同的功能, 但是它們有很大的區(qū)別;





2. 函數(shù) 和 宏 的分析


(1) 函數(shù) 和 宏 分析


函數(shù) 和 宏 分析 :

  • 1.宏處理 : 宏定義是在預(yù)處理階段直接進(jìn)行宏替換, 代碼直接復(fù)制到宏調(diào)用的位置, 由于宏在預(yù)處理階段就被處理了, 編譯器是不知道宏的存在的;
  • 2.函數(shù)處理 : 函數(shù)是需要編譯器進(jìn)行編譯的, 編譯器有決定函數(shù)調(diào)用行為的義務(wù);
  • 3.宏的弊端 ( 代碼量 ) : 每調(diào)用一次宏, 在預(yù)處理階段都要進(jìn)行一次宏替換, 會造成代碼量的增加;
  • 4.函數(shù)優(yōu)勢 ( 代碼量 ) : 函數(shù)執(zhí)行是通過跳轉(zhuǎn)來實(shí)現(xiàn)的, 代碼量不會增加;
  • 5.宏的優(yōu)勢 ( 效率 ) : 宏 的執(zhí)行效率 高于 函數(shù), 宏定義是在預(yù)編譯階段直接進(jìn)行代碼替換, 沒有調(diào)用開銷;
  • 6.函數(shù)的弊端 ( 效率 ) : 函數(shù)執(zhí)行的時(shí)候需要跳轉(zhuǎn), 以及創(chuàng)建對應(yīng)的活動記錄( 棧 ), 效率要低于宏;




3. 函數(shù) 與 宏 的 利弊


(1) 宏 優(yōu)勢 和 弊端


宏的優(yōu)勢和弊端 : 宏的執(zhí)行效率要高于函數(shù), 但是使用宏會有很大的副作用, 非常容易出錯(cuò), 下面的例子說明這種弊端;

代碼示例 :

  • 1.代碼 :
#include <stdio.h>#define ADD(a, b) a + b #define MUL(a, b) a * b #define _MIN_(a, b) ((a) < (b) ? (a) : b)int main() {int a = 1, b = 10;//宏替換的結(jié)果是 : 2 + 3 * 4 + 5, 最終打印結(jié)果是 19printf("%d\n", MUL(ADD(2, 3), ADD(4, 5)));//宏替換的結(jié)果是 ((a++) < (b) ? (a++) : b), 打印結(jié)果是 2printf("%d\n", _MIN_(a++, b));return 0; }
  • 2.編譯運(yùn)行結(jié)果 :
  • 3.查看預(yù)編譯文件 : 使用 gcc -E test_1.c -o test_1.i 指令, 將預(yù)編譯文件輸出到 test_1.i 目錄中; 下面是預(yù)編譯文件的一部分 ;
int main() {int a = 1, b = 10;printf("%d\n", 2 + 3 * 4 + 5);printf("%d\n", ((a++) < (b) ? (a++) : b));return 0; }


(2) 函數(shù) 的 優(yōu)勢 和 弊端


函數(shù)的優(yōu)缺點(diǎn) :

  • 1.函數(shù)優(yōu)勢 : 函數(shù)調(diào)用需要將實(shí)參傳遞給形參, 沒有宏替換這樣的副作用;
  • 2.弊端 ( 效率低 ) : 函數(shù)執(zhí)行需要跳轉(zhuǎn), 同時(shí)也需要建立活動對象對象 ( 如 函數(shù)棧 ) 來存儲相關(guān)的信息, 需要犧牲一些性能;


(3) 宏的無可替代性


宏 定義 優(yōu)勢 :

  • 1.宏參數(shù)不限定類型 : 宏參數(shù) 可以是 任何 C 語言 的實(shí)體類型, 如 int, float, char, double 等;
  • 2.宏參數(shù)可以使類型名稱 : 類型的名稱也可以作為宏的參數(shù);
//宏定義 : 實(shí)現(xiàn)分配 n 個(gè) type 類型空間, 并返回 type 類型指針 #define MALLOC(type, n) (type*)malloc(n * sizeof(type))//分配 5 個(gè) float 大小的動態(tài)空間, 并將首地址存放在 指針 p 中; float *p = MALLOC(int, 5);




4. 總結(jié)


(1) 宏 定義 和 函數(shù) 總結(jié)


宏定義 和 函數(shù) 小結(jié) :

  • 1.宏定義 : 宏 的 參數(shù) 可以 是 C 語言中 的 任何類型的 ( 優(yōu)勢 ) , 宏的執(zhí)行效率 高 ( 優(yōu)勢 ), 但是容易出錯(cuò) ( 弊端 );
  • 2.函數(shù) : 函數(shù) 參數(shù) 的 類型是固定的, 其 執(zhí)行效率低于宏, 但是不容易出錯(cuò);
  • 3.宏定義 和 函數(shù)之間的關(guān)系 : 這兩者不是競爭對手, 宏定義可以實(shí)現(xiàn)一些函數(shù)無法實(shí)現(xiàn)的功能;






四. 函數(shù)的調(diào)用約定




1. 函數(shù)的活動記錄 分析


(1) 函數(shù)的活動記錄


活動記錄概述 : 函數(shù)調(diào)用時(shí) 將 下面一系列的信息 記錄在 活動記錄中 ;

  • 1.臨時(shí)變量域 : 存放一些運(yùn)算的臨時(shí)變量的值, 如自增運(yùn)算, 在到順序點(diǎn)之前的數(shù)值是存在臨時(shí)變量域中的;

    后置操作 自增 原理 : i++ 自增運(yùn)算 進(jìn)行的操作 :
    ( 1 ) 生成臨時(shí)變量 : 在內(nèi)存中生成臨時(shí)變量 tmp ;
    ( 2 ) 臨時(shí)變量賦值 : 將 i 的值賦值給臨時(shí)變量, tmp = i ;
    ( 3 ) 進(jìn)行加 1 操作 : 將 i + 1 并賦值給 i;

    示例 : 定義函數(shù) fun(int a, int b), 傳入 fun(i, i++), 傳入后 獲取的實(shí)參值分別是 2 和 1;
    在函數(shù)傳入?yún)?shù)達(dá)到順序點(diǎn)之后開始取值, 函數(shù)到達(dá)順序點(diǎn)之后, 上面的三個(gè)步驟就執(zhí)行完畢, 形參 a 從內(nèi)存中取值, i 的值是2, 形參 b 從臨時(shí)變量域中取值, 即 tmp 的值, 取值是 1;

  • 2.局部變量域 : 用于存放 函數(shù) 中定義 的局部變量, 該變量的生命周期是局部變量執(zhí)行完畢;

  • 3.機(jī)器狀態(tài)域 : 保存 函數(shù)調(diào)用 之前 機(jī)器狀態(tài) 相關(guān)信息, 包括 寄存器值 和 返回地址, 如 esp 指針, ebp 指針;
  • 4.實(shí)參數(shù)域 : 保存 函數(shù)的實(shí)參信息 ;
  • 5.返回值域 : 存放 函數(shù)的返回值 ;




2. 函數(shù)的調(diào)用約定概述


(1) 參數(shù)入棧 問題描述


參數(shù)入棧問題 : 函數(shù)參數(shù)的計(jì)算次序是不固定的, 嚴(yán)重依賴于編譯器的實(shí)現(xiàn), 編譯器中函數(shù)參數(shù)入棧次序;

  • 1.參數(shù)傳遞順序 : 函數(shù)的參數(shù) 實(shí)參傳遞給形參 是從左到右傳遞 還是 從右到左傳遞;
  • 2.堆棧清理 : 是函數(shù)的調(diào)用者清理 還是 由 函數(shù)本身清理 ;


參數(shù)入棧 棧維護(hù) 問題示例 :

  • 1.多參數(shù)函數(shù)定義 : 定義一個(gè)函數(shù) fun(int a, int b, int c) , 其中有 3 個(gè)參數(shù);
  • 2.函數(shù)調(diào)用 : 當(dāng)發(fā)生函數(shù)調(diào)用時(shí) fun(1, 2, 3), 傳入三個(gè) int 類型的參數(shù), 這三個(gè)參數(shù)肯定有一個(gè)傳遞順序, 這個(gè)傳遞順序可以約定;
    • ( 1 ) 從左向右入棧 : 將 1, 2, 3 依次 傳入 函數(shù)中 ;
    • ( 2 ) 從右向左入棧 : 將 3, 2, 1 依次 傳入 函數(shù)中 ;
  • 3.棧維護(hù) : 在 fun1() 函數(shù)中 調(diào)用 fun2() 函數(shù), 會創(chuàng)建 fun2() 函數(shù)的 活動記錄 (棧), 當(dāng) fun2() 函數(shù)執(zhí)行完畢 返回的時(shí)候, 該 fun2 函數(shù)的棧空間是由誰 ( fun1 或 fun2 函數(shù) ) 負(fù)責(zé)釋放的;

函數(shù)參數(shù)計(jì)算次序依賴于編輯器實(shí)現(xiàn), 函數(shù)參數(shù)入棧的順序可以自己設(shè)置;



(2) 參數(shù)傳遞順序的調(diào)用約定


函數(shù)參數(shù)調(diào)用約定 :

  • 1.函數(shù)調(diào)用行為 : 函數(shù)調(diào)用時(shí) 參數(shù) 傳遞給 被調(diào)用的 函數(shù), 返回值被返回給 調(diào)用函數(shù) ;
  • 2.調(diào)用約定作用 : 調(diào)用約定 是 用來規(guī)定 ① 參數(shù) 是通過什么方式 傳遞到 棧空間 ( 活動記錄 ) 中, ② 棧 由誰來 清理 ;
  • 3.參數(shù)傳遞順序 ( 右到左 ) : 從右到左入棧使用 __stdcall, __cdecl, __thiscall 關(guān)鍵字, 放在 函數(shù)返回值之前;
  • 4.參數(shù)傳遞順序 ( 左到右 ) : 從左到右入棧使用 __pascal, __fastcall 關(guān)鍵字, 放在 函數(shù)返回值之前;
  • 5.調(diào)用堆棧的清理工作 : ① 調(diào)用者負(fù)責(zé)清理調(diào)用堆棧; ② 被調(diào)用的函數(shù)返回之前清理堆棧;






五. 函數(shù)設(shè)計(jì)技巧





函數(shù)設(shè)計(jì)技巧 :

  • 1.避免使用全局變量 : 在函數(shù)中盡量避免使用全局變量, 讓函數(shù)形成一個(gè)獨(dú)立功能模塊;
  • 2.參數(shù)傳遞全局變量 : 如果必須使用到全局變量, 那么多設(shè)計(jì)一個(gè)參數(shù), 用于傳入全局變量;
  • 3.參數(shù)名稱可讀性 : 盡量不要使用無意義的字符串作為參數(shù)變量名;
  • 4.參數(shù)常量 : 如果參數(shù)是一個(gè)指針, 該指針僅用于輸入作用, 盡量使用 const 修飾該指針參數(shù), 防止該指針在函數(shù)體內(nèi)被修改;
//這里第二個(gè)參數(shù)僅用于輸入, 不需要修改該指針, 那么就將該參數(shù)設(shè)置成常量參數(shù) void fun(char *dst, const char* src);
  • 5.返回類型不能省略 : 函數(shù)的返回類型不能省略, 如果省略了返回值, 那么返回值默認(rèn) int;
  • 6.參數(shù)檢測 : 在函數(shù)開始位置, 需要檢測函數(shù)參數(shù)的合法性, 避免不必要的錯(cuò)誤, 尤其是指針類型的參數(shù);
  • 7.棧內(nèi)存指針 : 返回值 絕對不能是 局部變量指針, 即 指針指向的位置是 棧內(nèi)存位置, 棧內(nèi)存在返回時(shí)會銷毀, 不能再函數(shù)運(yùn)行結(jié)束后使用 ;
  • 8.代碼量 : 函數(shù)的代碼量盡量控制在一定數(shù)目, 50 ~ 80 行, 符合模塊化設(shè)計(jì)規(guī)則;
  • 9.輸入輸出固定 : 函數(shù)在輸入相同的參數(shù), 其輸出也要相同, 盡量不要在函數(shù)體內(nèi)使用 static 局部變量, 這樣函數(shù)帶記憶功能, 增加函數(shù)的復(fù)雜度;
  • 10.參數(shù)控制 : 編寫函數(shù)的時(shí)候, 函數(shù)的參數(shù)盡量控制在 4 個(gè)以內(nèi), 方便使用;
  • 11.函數(shù)返回值設(shè)計(jì) : 有時(shí)候函數(shù)不需要返回值, 或者返回值使用指針參數(shù)設(shè)置, 但是為了增加靈活性, 可以附加返回值; 如 支持 鏈?zhǔn)奖磉_(dá)式 功能;

總結(jié)

以上是生活随笔為你收集整理的【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。