C語言可變參數(shù)列表的用法與深度剖析
前言
可變參數(shù)列表,使用起來像是數(shù)組,學(xué)習(xí)過函數(shù)棧幀的話可以發(fā)現(xiàn)實(shí)際上他也就是在棧區(qū)定義的一塊空間當(dāng)中連續(xù)訪問,不過他不支持直接在中間部分訪問。
聲明: 以下所有測(cè)試都是在x86,vs2013下完成的。
一、可變參數(shù)列表是什么?

在我們初始C語言的第一節(jié)課的時(shí)候我們就已經(jīng)接觸了可變參數(shù)列表,在printf的過程當(dāng)中我們通??梢詡鬟f大量要打印的參數(shù),但是我們卻不知道他是如何做到的,今天就帶大家剖析它。
二、怎么用可變參數(shù)列表
首先我們要引入windows.h的頭文件
然后我們先要介紹以下幾個(gè)宏。在這里我們先簡述它的功能,在后面會(huì)有詳細(xì)的講解,這里是為了方便大家入門。
typedef char* va_list; //類型的重定義 #define _ADDRESSOF(v) (&(v))//一個(gè)取地址的宏。
1._ADDRESSOF:取傳入變量的地址。
#define _INTSIZEOF(n) \ ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
2._INTSIZEOF:該宏功能是讓n的類型往4的倍數(shù)上取整。
#define _INTSIZEOF(n)\ ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
下面一段代碼進(jìn)行解釋:
#pragma pack(1)//設(shè)置默認(rèn)對(duì)其數(shù)為1
struct A
{
char ch[11];
};
int main()
{
printf("int : %d\n", _INTSIZEOF(int));
printf("double: %d\n", _INTSIZEOF(double));
printf("short: %d\n", _INTSIZEOF(short));
printf("float: %d\n", _INTSIZEOF(float));
printf("long long int: %d\n", _INTSIZEOF(long long int));
printf("struct A:%d\n", _INTSIZEOF(struct A));
return 0;
}
結(jié)果:

3.__crt_va_start_a:取變量v的地址強(qiáng)轉(zhuǎn)為char*然后向指向v類型對(duì)其數(shù)后,即找到第一個(gè)可變參數(shù)列表當(dāng)中的變量!
#define __crt_va_start_a(ap, v) \ ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
4.__crt_va_arg:將ap提前指向下一個(gè)要訪問的位置,并且返回當(dāng)前訪問的內(nèi)容。 注意+=后ap指向下一個(gè)要訪問的地址,但是返回的內(nèi)容是當(dāng)前的。
#define __crt_va_arg(ap, t) \
(*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
5.__crt_va_end:將ap置成NULL。
#define __crt_va_end(ap)\ ((void)(ap = (va_list)0))
緊接著我們看一下以下幾個(gè)定義。
#define va_start __crt_va_start #define va_arg __crt_va_arg #define va_end __crt_va_end
測(cè)試:找一組不存放在數(shù)組當(dāng)中的最大的一個(gè)數(shù)據(jù)返回。
int Find_Max(int num, ...)
{
//定義一個(gè)char* 的變量arg
va_list arg;
//將arg指向第一個(gè)可變參數(shù)
va_start(arg, num);
//將max置成第一個(gè)可變參數(shù),然后arg指向下一個(gè)可變參數(shù)
int max = va_arg(arg, int);
//循環(huán)num-1次,訪問完剩下的可變參,找到最大的賦值給max
for (int i = 1; i < num; ++i)
{
int r;
if (max < (r = va_arg(arg, int)))
{
max = r;
}
}
//將arg指針變量置成NULL,避免野指針
va_end(arg);
return max;
}
int main()
{
int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
printf("ret :%d\n", ret);
return 0;
}
結(jié)果:

三、對(duì)于宏的深度剖析
雖然在Linux下的進(jìn)程地址空間是由高到低排列的,但是由于vs下的內(nèi)存是從低字節(jié)到高字節(jié)的,我們的棧會(huì)和linux下畫的不太一樣,但是都是朝著低地址方向擴(kuò)展的。這是方便大家理解。
Linux的進(jìn)程地址空間示意圖:

代碼棧幀示意圖:

隱式類型轉(zhuǎn)換
舉個(gè)栗子,當(dāng)我們執(zhí)行下面的代碼,當(dāng)我們以char類型傳參,但函數(shù)體依舊以int的步長獲取,此時(shí)會(huì)出錯(cuò)嗎?
#include<stdio.h>
#include<windows.h>
int Find_Max(int num, ...)
{
//定義一個(gè)char* 的變量arg
va_list arg;
//將arg指向第一個(gè)可變參數(shù)
va_start(arg, num);
//將max置成第一個(gè)可變參數(shù),然后arg指向下一個(gè)可變參數(shù)
int max = va_arg(arg, int);
//循環(huán)num-1次,訪問完剩下的可變參,找到最大的賦值給max
for (int i = 1; i < num; ++i)
{
int r;
if (max < (r = va_arg(arg, int)))
{
max = r;
}
}
//將arg指針變量置成NULL,避免野指針
va_end(arg);
return max;
}
int main()
{
char a = '1'; //ascii值: 49
char b = '2'; //ascii值: 50
char c = '3'; //ascii值: 51
char d = '4'; //ascii值: 52
char e = '5'; //ascii值: 53
int ret = Find_Max(5, a, b, c, d, e);
//int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
printf("ret :%d\n", ret);
system("pause");
}
答案:
不會(huì)的,由于壓棧的時(shí)候是通過寄存器傳參的,32位下的寄存器就是4個(gè)字節(jié)。

壓棧時(shí)的匯編:其中第一條不是mov,而是movsx,即匯編語言數(shù)據(jù)傳送指令MOV的變體。帶符號(hào)擴(kuò)展,并傳送。也就是整形提升。

同理:用float傳參,用double字長走,也是沒有問題的。

總結(jié):
所以我們習(xí)慣在函數(shù)體內(nèi)部(Find_Max)用int/double為長度走,而傳參的時(shí)候我們可以用char/short/float等等類型。
注意:
64位下的定義和32位下差異是很大的。
為什么按照4字節(jié)對(duì)齊:
先前講到在短整型,在壓棧的過程中會(huì)發(fā)生整形提升,那么從棧幀中要拿到對(duì)應(yīng)的數(shù)據(jù)也要按照對(duì)應(yīng)的方法提取。
_INTSIZEOF的數(shù)學(xué)理解:
_INTSIZEOF(n)的意思:計(jì)算一個(gè)最小數(shù)字x,滿足 x>=n && x%4==0,n表示sizeof(n)的值。即該類型的大小要滿足往n的整數(shù)倍對(duì)齊,且最小不能小于n。
以4字節(jié)對(duì)齊為栗子:
n%4 == 0,則 ret = n;
n %4 != 0 , 則 ret = (n+ 4 - 1)/4 *4;
(n+ 4 - 1)/4 -->假設(shè) n為1到4,那么(n + 4 - 1)/4的結(jié)果都是1,再乘上對(duì)其數(shù)4就是以4對(duì)齊的最小對(duì)齊數(shù)了。就能將這4個(gè)數(shù)值范圍的最小對(duì)齊倍數(shù)控制在同一個(gè)值。
我們觀察(n+ 4 -1)/4 *4,/4實(shí)際上就是將二進(jìn)制序列往右移,*4就是把二進(jìn)制序列往左移動(dòng),這一來一回實(shí)際上就是把最低兩位置成0,那么我們還可以簡化成:
(n+ 4 -1) & ~3 ,也就是源碼當(dāng)中的定義了!!
#define _INTSIZEOF(n)\ ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
對(duì)兩個(gè)函數(shù)的重新認(rèn)知
對(duì)printf的理解:
在上述的例子中,宏是無法判斷實(shí)際存在參數(shù)的數(shù)量,以及實(shí)際參數(shù)的類型的,那么在printf當(dāng)中,必定有能夠確定參數(shù)數(shù)量以及辨別參數(shù)類型的方法,實(shí)際上也就是%c,%d,%lf,其中%的數(shù)量除了%%外的%的數(shù)量實(shí)際上就能讓我們得知參數(shù)的數(shù)量,而%c,%d,實(shí)際上也就說明了對(duì)應(yīng)的類型。
對(duì)exec系列的理解:

在進(jìn)程控制,當(dāng)時(shí)講述了實(shí)際上只有一個(gè)系統(tǒng)調(diào)用execve,其他函數(shù)exec函數(shù)最終都是要調(diào)用execve函數(shù),那么是如何實(shí)現(xiàn)從參數(shù)l到v這個(gè)過程的呢?
答案:
實(shí)際上訪問到null為止,傳參的數(shù)量用一個(gè)count一直計(jì)數(shù)就能拿到,而類型毫無疑問就是char*,我們可以用strlen去計(jì)算要走多長。(不過兩個(gè)char數(shù)組通常會(huì)間隔多8個(gè)字節(jié))
總結(jié)
到此這篇關(guān)于C語言可變參數(shù)列表的文章就介紹到這了,更多相關(guān)C語言可變參數(shù)列表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
QT獲取顯示當(dāng)前時(shí)間和日期的方法(用QTime,QDate和QDateTime)
獲取當(dāng)期日期時(shí)間在我們?nèi)粘i_發(fā)中經(jīng)常會(huì)遇到,下面這篇文章主要給大家介紹了關(guān)于QT獲取顯示當(dāng)前時(shí)間和日期的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
C++?select模型簡單聊天室的實(shí)現(xiàn)示例
本文主要介紹了C++?select模型簡單聊天室的實(shí)現(xiàn)示例,使用CMake項(xiàng)目進(jìn)行開發(fā),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
C++實(shí)例分析講解臨時(shí)對(duì)象與右值引用的用法
對(duì)性能來說,許多的問題都需要和出現(xiàn)頻率及本身執(zhí)行一次的開銷掛鉤,有些問題雖然看似比較開銷較大,但是很少會(huì)執(zhí)行到,那也不會(huì)對(duì)程序有大的影響;同樣一個(gè)很小開銷的函數(shù)執(zhí)行很頻繁,同樣會(huì)對(duì)程序的執(zhí)行效率有很大影響。本章中作者主要根據(jù)臨時(shí)對(duì)象來闡述這樣一個(gè)觀點(diǎn)2022-08-08
C++實(shí)現(xiàn)將s16le的音頻流轉(zhuǎn)換為float類型
這篇文章主要為大家詳細(xì)介紹了如何利用C++實(shí)現(xiàn)將s16le的音頻流轉(zhuǎn)換為float類型,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-04-04
C++實(shí)現(xiàn)學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07
一文帶你入木三分地理解字符串KMP算法以及C++實(shí)現(xiàn)
KMP算法是一種改進(jìn)的字符串匹配算法,KMP算法的核心是利用匹配失敗后的信息,盡量減少模式串與主串的匹配次數(shù)以達(dá)到快速匹配的目的。本文就來和大家聊聊KMP算法的原理與實(shí)現(xiàn),需要的可以參考一下2022-12-12
Qt自定義實(shí)現(xiàn)一個(gè)等待提示Ui控件
等待樣式控件是我們?cè)谧鯱I時(shí)出場(chǎng)率還挺高的控件之一,所以這篇文章主要為大家介紹了Qt如何自定義一個(gè)好看的等待提示Ui控件,感興趣的可以了解下2024-01-01

