C語言的那些小秘密之變參函數的實現

2020-12-13 電子產品世界

  在學習C語言的過程中我們可能很少會去寫變參函數,印象中大學老師好像也沒有提及過,但我發現變參函數的實現很巧妙,所以還是特地在此分析下變參函數的實現原理。無需標準C的支持,我們自己寫代碼來實現。

本文引用地址:http://www.eepw.com.cn/article/270588.htm

  先來看看一個實現代碼:

  #include

  #define va_list void*

  #define va_arg(arg, type) *(type*)arg; arg = (char*)arg + sizeof(type);

  #define va_start(arg, start) arg = (va_list)(((char*)&(start)) + sizeof(start))

  int sum(int nr, ...)

  {

  int i = 0;

  int result = 0;

  va_list arg = NULL;

  va_start(arg, nr);

  for(i = 0; i < nr; i++)

  {

  result += va_arg(arg, int);

  }

  return result;

  }

  int main(int argc, char* argv[])

  {

  printf("%d\n", sum(4, 100,100,100,100));

  printf("%d\n", sum(3, 200, 200, 200));

  return 0;

  }

  運行結果如下:



  #define va_list void*通過這句代碼我們實現了定義va_list是一個指針,參數類型不定,它可以指向任意類型的指針。為了讓arg指向第一個可變參數,我們用nr的地址加上nr的數據類型大小就行了,採用如下的定義可以實現。

  #define va_start(arg, start) arg = (va_list)(((char*)&(start)) + sizeof(start)) 。

  通過(((char*)&(start)) + sizeof(start)) 可以得到第一個可變參數的地址,再將其強制轉換為va_list類型。

  成功取出了第一個可變參數後,接下來的任務就是繼續取出可變參數,方法跟上面求第一個可變參數的方法一樣,通過arg = (char*)arg + sizeof(type);來實現讓arg指向下一個可變參數,type為可變參數的類型,通過這種方法可以一一取出可變參數。

  在這裡順便給出上面實現代碼的彙編代碼,有興趣的可以讀讀,加深下對於底層彙編代碼的閱讀能力。

  .file "varargs.c"

  .text

  .globl sum

  .type sum, @function

  sum:

  pushl %ebp

  movl %esp, %ebp

  subl $16, %esp

  movl $0, -4(%ebp)

  movl $0, -8(%ebp)

  movl $0, -12(%ebp)

  leal 12(%ebp), %eax

  movl %eax, -12(%ebp)

  movl $0, -4(%ebp)

  jmp .L2

  .L3:

  movl -12(%ebp), %eax

  movl (%eax), %eax

  addl %eax, -8(%ebp)

  addl $4, -12(%ebp)

  addl $1, -4(%ebp)

  .L2:

  movl 8(%ebp), %eax

  cmpl %eax, -4(%ebp)

  jl .L3

  movl -8(%ebp), %eax

  leave

  ret

  .size sum, .-sum

  .section .rodata

  .LC0:

  .string "%d\n"

  .text

  .globl main

  .type main, @function

  main:

  pushl %ebp

  movl %esp, %ebp

  andl $-16, %esp

  subl $32, %esp

  movl $100, 16(%esp)

  movl $100, 12(%esp)

  movl $100, 8(%esp)

  movl $100, 4(%esp)

  movl $4, (%esp)

  call sum

  movl $.LC0, %edx

  movl %eax, 4(%esp)

  movl %edx, (%esp)

  call printf

  movl $200, 12(%esp)

  movl $200, 8(%esp)

  movl $200, 4(%esp)

  movl $3, (%esp)

  call sum

  movl $.LC0, %edx

  movl %eax, 4(%esp)

  movl %edx, (%esp)

  call printf

  movl $0, %eax

  leave

  ret

  .size main, .-main

  .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"

  .section .note.GNU-stack,"",@progbits

樹莓派文章專題:樹莓派是什麼?你不知道樹莓派的知識和應用

c語言相關文章:c語言教程


相關焦點

  • C語言的那些小秘密之預處理
    看到這兒你是不是想起我們之前的那篇《C語言的那些小秘密之斷言》了呢,我們同樣可以使用這種方法來實現斷言的關閉,方法與之類似,在此就不再講解了,有興趣的讀者可以自己試試。講到這兒似乎應該結束了,但是細心的讀者會有另外一個疑惑?
  • C語言的那些小秘密之函數的調用關係
    顯示函數的調用關係是調試器的必備功能,如果我們在程序的運行中出現了崩潰的情況,通過函數的調用關係可以快速定位問題的根源,懂得函數調用關係的實現原理也可以擴充自己的知識面,在沒有調試器的情況下,我們也可以自己來實現顯示函數的調用關係。
  • C語言的那些小秘密之異常處理
    如果我們在上面的代碼中稍作修改,在setjmp()函數的調用之前調用longjmp()函數,我們發現此時沒有任何的輸出,程序直接崩潰掉退出了。  接下來我們來看看一個函數的使用,如果對於這個函數不理解的讀者,可以多看幾次我給出的模擬該函數的實現代碼。
  • C語言的那些小秘密之volatile
    在開始講解volatile之前我們先來講解下接下來要用到的一個函數,知道如何使用該函數的讀者可以跳過該函數的講解部分。我們在此主要的是關注前一個成員變量timeval,後一個我們在此不使用,所以使用gettimeofday()函數的時候我們把有一個參數設定為NULL,下面先來看看一段簡單的代碼。
  • C語言的那些小秘密之函數指針
    我們經常會聽到這樣的說法,不懂得函數指針就不是真正的C語言高手。我們不管這句話對與否,但是它都從側面反應出了函數指針的重要性,所以我們還是有必要掌握對函數指針的使用。先來看看函數指針的定義吧。本文引用地址:http://www.eepw.com.cn/article/270442.htm  函數是由執行語句組成的指令序列或者代碼,這些代碼的有序集合根據其大小被分配到一定的內存空間中,這一片內存空間的起始地址就成為函數的地址,不同的函數有不同的函數地址,編譯器通過函數名來索引函數的入口地址,為了方便操作類型屬性相同的函數,c/c++引入了函數指針,函數指針就是指向代碼入口地址的指針
  • C語言的那些小秘密之字節對齊
    按照預先的計劃安排,這次應該是寫《C語言的那些小秘密之鍊表(三)》的,但是我發現如果直接開始講解linux內核鍊表的話,可能有些地方如果我們不在此做一個適當的講解的話,有的讀者看起來可能難以理解,所以就把字節對齊挑出來另寫一篇博客,我在此儘可能的講解完關於字節對齊的內容,希望我的講解對你有所幫助。
  • C語言的那些小秘密之動態數組
    不管什麼情況下通通使用靜態數組的方法來解決,在當初學習C語言的時候我就是一個典型的例子,但是現在發現這是一個相當不好的習慣,甚至可能導致編寫的程序出現一些致命的錯誤。  請輸入所要創建的一維動態數組的長度:4  0 0 0 0  1 2 3 4 Press any key to continue  在此我使用的是calloc()函數來分配的,同時也使用兩個for語句來列印數組元素,我們發現第一個列印輸出的數組元素值均為0,在此也是為了加深讀者對於calloc()函數的印象我特地使用了它來分配,如果對於calloc
  • C語言的那些小秘密之斷言
    在以上代碼的開頭部分我們把#define NDEBUG給注釋掉了,所以我們啟用了assert,main函數中使用了assert(copy_string(str,dec_str));來實現copy_string函數的調用
  • C語言函數指針之回調函數
    我的理解是:把一段可執行的代碼像參數傳遞那樣傳給其他代碼,而這段代碼會在某個時刻被調用執行,這就叫做回調如果代碼立即被執行就稱為同步回調,如果過後再執行,則稱之為異步回調回調函數就是一個通過調用的函數。
  • 現代C++函數式編程之function compose
    C++17實現function compose我們可以藉助C++17實現這樣一個compose,實現思路: 藉助變參,讓我們可以組合任意多個函數,然後從最後一個參數開始計算,最後一個參數就表示最後面的那個函數,將它的計算結果作為它前面一個函數的入參,依次類推直到最開始那個函數為止。
  • C語言中字符串和數字的相互轉換實現代碼
    以下是對C語言中字符串和數字的相互轉換實現代碼進行了分析介紹,需要的朋友可以參考下1.數字轉換為字符串sprintf 是個變參函數,定義如下:int sprintf( char *buffer, const char *format [, argument] ... );除了前兩個參數類型固定外,後面可以接任意多個參數。
  • C語言之函數
    一.函數的概念1.C語言中,最簡單的程序模塊就是函數。2.函數被視為程序設計的基本邏輯單位,一個c程序是由一個main()函數和若干其他函數組成。3.程序執行從main()函數開始,main()函數可以調用其他函數,其他函數可以互相調用。
  • golang函數func介紹
    golang函數func介紹函數是基本的代碼塊,用於執行一個任務。Go 語言最少有個 main() 函數。
  • Go函數用法實戰
    Go函數用法實戰Go語言是一門非常容易上手的語言,在以後的文章中,儘量簡單且實用!
  • 常用C庫函數的實現
    實現C語言庫函數我們在課上也經常會給大家寫,但是都不夠全面。所以今天就給大家總結了一下。常見C庫函數的實現代碼奉上size_t n);void *memset(void *s, int c, size_t n){    void *ret = s;        while(n--) {        *(char *)s = (char)c;        s = (char *)s + 1;  } return ret; }
  • 深入淺出剖析C語言函數指針與回調函數(一)
    我們把函數的指針(地址),這裡也就是add_ret,作為參數int add(int a , int b , int (*add_value)()) , 這裡的參數就是int(*add_value)() , 這個名字可以隨便取,但是要符合C語言的命名規範。當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。
  • 傳智播客:C語言函數對另外一個源文件函數進行調用(外部函數)
    當一個程序由多個源文件組成的時候,根據函數是否能被其他源文件調用的時候,將函數分為內部函數和外部函數,本文就會圍著這外部函數的特點進行講解,希望每一個在學C語言的小夥伴都能弄懂函數的知識點。外部函數在開發大的項目的時候,為了方便團隊的協同工作,我們需要把一個項目拆分開,分成很多的源文件來實現。最後再將它們整理在一起。為了減少不必要的重複代碼,一個源文件有時候需要調用其他的源文件中定義的函數。
  • 學習c語言筆記——C庫函數printf()
    c語言中的printf是什麼來的?」。我答:「它是一個函數,主要用來輸出運算結果。」 ,下面就給大家介紹C庫函數printf()使用方法。下面我們通過一個調用c庫函數的c語言案例來說明printf()函數的使用方法,如c語言1。
  • 深度剖析C語言的main函數
    /a.out && echo "hello world"  #&&與運算,前面為真,才會執行後邊的c 語言可以看出,作業系統認為main函數執行失敗,因為main函數的返回值是11➜  testSigpipe git:(master) ✗ .
  • 為何C語言函數調用要堆棧,而彙編卻不需要?
    然後待被調用的子函數執行完畢的時候,再調用pop,把堆棧中的一個個的值,賦值給對應的那些你剛開始壓棧時用到的寄存器,把對應的值從堆棧中彈出去,即所謂的出棧。其中保存的寄存器中,也包括lr的值(因為用bl指令進行跳轉的話,那麼之前的pc的值是存在lr中的),然後在子程序執行完畢的時候,再把堆棧中的lr的值pop出來,賦值給pc,這樣就實現了子函數的正確的返回。