C语言可变参数只会用算啥本事?看我来抽丝剥茧干翻它!
看山是山,看山不是山,最終看山才是山,并且是無窮的山巒。當我們學習一門技術的時候,起初是先模仿,但是最終是為了超越,也就是得到秘籍,看到本質。
于是,今天來繼續(xù)看可變參數,我們來分析這個過程,代碼如下:
聲明int add (int num,...);...會說明是個可變函數,這樣子編譯器在編譯遇到的函數的地方,就知道自動解析,依據傳入的參數,直接進行進棧,從而不需要報錯。
我們默認的函數,如果聲明是兩個參數,調用的是三個參數的話,最終會出錯,會提示你找不到實現體。
聲明完了后,實現函數,然后我們在使用的地方,直接傳入多個參數,就不會出問題。
我們這里要看下add里面的具體實現,第一個參數我們利用這個值,用來判定長度,循環(huán)的結束點。
然后使用va_start 來卡找到第一個參數的地址,我這節(jié)畫個圖,大家就能理解了。再一個你也就明白了,為什么是int add(int num,...);而不是int add(...); 因為如果是這個,你實現的時候,就沒法定位到起始地址,導致你無法解析后續(xù)的參數,這個明白了吧。
所以,編譯時候,系統(tǒng)會提示錯誤,規(guī)定...參數前面必須有一個有名參數,否則系統(tǒng)編譯的時候,你在實現體里面,無法做處理。這里就是?va_start(valist,num);
然后我們使用va_arg 去獲取后續(xù)的參數,第一個是起始地址,第二個是后面緊跟著的數據,該以哪個大小去解析。我們這里是int,都是這個尺寸,所以用了循環(huán)。
在printf里面,使用的是%s ,%f這類處理,依據這個會變化。
使用完成后,就可以用va_end來結束指針。
我們如果把調用地方改成add(5,a,b);最終能輸出出來結果,但是非常亂,原因很簡單,這里的第一個參數5,讓遍歷尋找了棧上面的一些臟數據,導致結果未知。
我們如果把int b=6改成float b=6;會發(fā)生什么神奇的現象呢?出現了神奇的結果,原因很簡單,float 進入棧的時候,占用的空間比int大,但是我們執(zhí)行的時候,用了int大小去解析了這個數據,導致出現問題。
我們來看下如何修正這個問題,就需要格式化處理了,第一個參數,我們把它調整下,變成char *format,我們把代碼改成這個,
為什么這里有float變?yōu)榱薲ouble,主要是方便系統(tǒng)進行處理,升級后你 解析的時候,就要用double去解析,否則的話你處理完數據,ap指針就沒有指到下一個位置,導致出錯。
不過這個問題現在你不需要擔心,如果你沒有寫對,系統(tǒng)會在編譯時候提醒你,直接系統(tǒng)報錯,讓你去修改的。
這樣子看下來,是不是覺得可變參數也沒多神奇了?簡單說下就是編譯器支持...讓函數參數可變,同時保留一個有名參數,讓實現體可以用這個去定位到起始位置,然后進行遍歷解析,完成邏輯處理。
我們把這個再抽象一層,簡單來說,就是一組數據約束,存放在一起,然后我們依據一個格式化參數,對這個數據進行解析處理。當你看到這個的時候,就突然明白,協(xié)議的概念。
協(xié)議就是約定,約定雙方同時遵循一個規(guī)則,同時遵守,就是協(xié)議。TCP/IP協(xié)議,ELF文件解析協(xié)議。
當你抽象到這里,基本上就大徹大悟,一切處理都是協(xié)議頭 數據( 校驗)。
在今天最后,我們來看下反編譯后的代碼,就明白了第一個參數的地址的意義,我這里用的int b=6;原因是如果是float的話,指令會比較復雜,不方便我們學習。
這里可以看到,我們地參數是 %d,%d??a b??這三個,在匯編代碼中,可以看到,
rbp-0x8 放的是b的值,6
rbp-0xc 放的是a的值,5
rbp-0x14,放著一個地址4007ec,這地方就是%d,%d 常量字串位置
所以我們add函數實現里面,fmt拿到的就是rbp-0x14的地址,也就是第一個參數,隨后的解析就是依據給的格式,把對應數據解析出來,移動指針ap的位置到合適地方(依據對齊原則,以及sizeof(數據類型))
聲明:
本文于網絡整理,版權歸原作者所有,如來源信息有誤或侵犯權益,請聯系我們刪除或授權事宜。
總結
以上是生活随笔為你收集整理的C语言可变参数只会用算啥本事?看我来抽丝剥茧干翻它!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 天猫精灵怎么多台同步(汉典天字的基本解释
- 下一篇: iOS 16中如何开启实时字幕iPhon