C語(yǔ)言可變參數(shù)與函數(shù)參數(shù)的內(nèi)存對(duì)齊詳解
什么是可變參數(shù)?
有時(shí),您可能會(huì)碰到這樣的情況,您希望函數(shù)帶有可變數(shù)量的參數(shù),而不是預(yù)定義數(shù)量的參數(shù)。
C 語(yǔ)言為這種情況提供了一個(gè)解決方案,它允許您定義一個(gè)函數(shù),能根據(jù)具體的需求接受可變數(shù)量的參數(shù)。
比如我們最常用的printf函數(shù),它的函數(shù)聲明是:int printf(const char *format, ...);該函數(shù)就是一個(gè)典型的應(yīng)用可變參數(shù)的實(shí)例,后面那三個(gè)...就是說(shuō)明該函數(shù)是可變參數(shù)函數(shù)。
使用可變參數(shù)
要使用可變函數(shù),得引用一個(gè)頭文件#include <stdarg.h>該文件提供了實(shí)現(xiàn)可變參數(shù)功能的函數(shù)和宏。
使用可變參數(shù)的步驟如下:
1.定義一個(gè)函數(shù),最后一個(gè)參數(shù)為省略號(hào)...,省略號(hào)前面可以設(shè)置自定義參數(shù)(至少得有一個(gè)固定參數(shù))。如int getSum(int num, ...)//定義可變參數(shù)的函數(shù)
2.在函數(shù)中定義va_list類型的變量list,該類型在stdarg.h中已定義。
3.使用宏函數(shù)va_start來(lái)初始化變量list,該宏函數(shù)在stdarg.h中已定義。
4.使用宏函數(shù)va_arg和list來(lái)訪問(wèn)參數(shù)列表中的每個(gè)項(xiàng)。
5.使用宏函數(shù)va_end來(lái)清理賦予list變量的內(nèi)存。
宏的聲明
/** \brief 初始化 ap 變量,它與 va_arg 和 va_end 宏是一起使用的。 * last_arg 是最后一個(gè)傳遞給函數(shù)的已知的固定參數(shù),即省略號(hào)之前的參數(shù)。 * 這個(gè)宏必須在使用 va_arg 和 va_end 之前被調(diào)用。 * * \param ap -- 這是一個(gè) va_list 類型的對(duì)象, * 它用來(lái)存儲(chǔ)通過(guò) va_arg 獲取額外參數(shù)時(shí)所必需的信息。 * \param last_arg -- 最后一個(gè)傳遞給函數(shù)的已知的固定參數(shù)(省略號(hào)前面的那個(gè)參數(shù))。 * \return 無(wú) * */ void va_start(va_list ap, last_arg) /** \brief 檢索函數(shù)參數(shù)列表中類型為 type 的下一個(gè)參數(shù)。它無(wú)法判斷檢索到的參數(shù)是否是傳給函數(shù)的最后一個(gè)參數(shù)。 * * \param ap -- 這是一個(gè) va_list 類型的對(duì)象,存儲(chǔ)了有關(guān)額外參數(shù)和檢索狀態(tài)的信息。 * 該對(duì)象應(yīng)在第一次調(diào)用 va_arg 之前通過(guò)調(diào)用 va_start 進(jìn)行初始化。 * \param type -- 這是一個(gè)類型名稱。該類型名稱是作為擴(kuò)展自該宏的表達(dá)式的類型來(lái)使用的。 * \return 該宏返回下一個(gè)額外的參數(shù),是一個(gè)類型為 type 的表達(dá)式。 * */ type va_arg(va_list ap, type) /** \brief 該宏允許使用了 va_start 宏的帶有可變參數(shù)的函數(shù)返回(釋放內(nèi)存)。如果在從函數(shù)返回之前沒(méi)有調(diào)用 va_end,則結(jié)果為未定義。 * * \param ap -- 這是之前由同一函數(shù)中的 va_start 初始化的 va_list 對(duì)象。 * \return 無(wú) * */ void va_end(va_list ap)
實(shí)例1一個(gè)可變參數(shù)的函數(shù),求和
#include <stdio.h>
#include <stdarg.h>//引用可變參數(shù)宏頭文件
int getSum(int num, ...)//定義可變參數(shù)的函數(shù)
{
int sum = 0;
va_list list;//創(chuàng)建va_list類型的變量
va_start(list, num);//初始化可變參數(shù)list
for(int i = 0; i < num; i++)
{
sum += va_arg(list, int);//訪問(wèn)參數(shù)列表中的每個(gè)項(xiàng)
}
va_end(list);//釋放內(nèi)存
return sum;
}
int main()
{
printf("%d\n", getSum(4, 4, 5, 6, 7));
}
實(shí)例2輸出字符串
#include <stdio.h>
#include <stdarg.h>//引用可變參數(shù)宏頭文件
void func(char *demo, ...)
{
char *pstr = NULL;
va_list list;
va_start(list, demo);
while(1)
{
pstr = va_arg(list, char *);
if(*pstr == '$')//以 '$' 代表結(jié)束
break;
printf("%s\n", pstr);
}
va_end(list);
}
int main()
{
func("demo", "ABC", "123", "Hello Wolrd!", '$');
}
這里特別注意一下,宏va_arg無(wú)法判斷檢索到的參數(shù)是否是傳給函數(shù)的最后一個(gè)參數(shù),所以我們需要告訴該參數(shù)是不是最后一個(gè)參數(shù),有2個(gè)方法,一是在使用一個(gè)函數(shù)參數(shù)來(lái)說(shuō)明可變參數(shù)的數(shù)量,一是定義一個(gè)結(jié)束標(biāo)志符。
可變參數(shù)的另外的一種使用方式
#include <stdio.h>
int getSum(int num, ...)
{
int sum = 0;
char *p = NULL;
p = (char*)#
p += 8;
for(int i = 0; i < num; i++)
{
sum += *((int*)p);
p += 8;
}
return sum;
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
printf("sum = %d\n", getSum(3, a, b, c));
}
/*
輸出結(jié)果
sum = 6;
*/
為什么這樣也可以訪問(wèn)可變參數(shù)呢?為什么指針p要加8呢?
因?yàn)檫@與函數(shù)參數(shù)的入棧出棧及函數(shù)參數(shù)的內(nèi)存對(duì)齊有關(guān)。
函數(shù)參數(shù)的內(nèi)存對(duì)齊
首先我們來(lái)看函數(shù)void func(int a, int b, int c)各個(gè)參數(shù)在棧中的位置
| c | 高地址 |
|---|---|
| b | ↓ |
| a | 低地址 |
函數(shù)參數(shù)的傳遞存儲(chǔ)在棧中,從右至左壓入棧中,壓棧過(guò)程為遞減;出棧過(guò)程為遞增。
所以我們只需要知道a的地址,在a的地址上加上偏移量就可以訪問(wèn)b或者c了。
那應(yīng)該加上多少偏移量呢?
#include <stdio.h>
void func(int a, int b, int c)
{
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
}
int main()
{
int a,b,c;
func(a, b, c);
}
/*
輸出結(jié)果
a = 000000000061FDF0
b = 000000000061FDF8
c = 000000000061FE00
*/
通過(guò)上例,發(fā)現(xiàn)它們之間相差8,為什么是8呢?
因?yàn)槲沂窃赪indow64位上運(yùn)行的,故需要按照8字節(jié)對(duì)齊。
綜上,函數(shù)參數(shù)的傳遞存儲(chǔ)在棧中,從右至左壓入棧中,壓棧過(guò)程為遞減,出棧過(guò)程為遞增;并且需要進(jìn)行內(nèi)存對(duì)齊,Window64位為8字節(jié)對(duì)齊,32位為4字節(jié)對(duì)齊。
下面是我做的一些實(shí)驗(yàn),更改了函數(shù)參數(shù)類型。
短整型
#include <stdio.h>
void func(char a, short b, long long c)
{
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
}
int main()
{
char a = 1;
short b = 2;
long long c = 3;
func(a, b, c);
}
/*
輸出結(jié)果
a = 000000000061FDF0
b = 000000000061FDF8
c = 000000000061FE00
*/
浮點(diǎn)型
#include <stdio.h>
void func(double a, double b, double c)
{
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
}
int main()
{
double a = 1;
double b = 2;
double c = 3;
func(a, b, c);
}
/*
輸出結(jié)果
a = 000000000061FDF0
b = 000000000061FDF8
c = 000000000061FE00
*/
結(jié)構(gòu)體1
#include <stdio.h>
typedef struct
{
char c[7];
}str_t;
void func(str_t a, str_t b, str_t c)
{
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
}
int main()
{
str_t a;
str_t b;
str_t c;
func(a, b, c);
}
/*
輸出結(jié)果
a = 000000000061FDF0
b = 000000000061FDE0
c = 000000000061FDD0
*/
結(jié)構(gòu)體2
#include <stdio.h>
typedef struct
{
char c[4];
}str_t;
void func(str_t a, str_t b, str_t c)
{
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
}
int main()
{
str_t a;
str_t b;
str_t c;
func(a, b, c);
}
/*
輸出結(jié)果
a = 000000000061FDF0
b = 000000000061FDF8
c = 000000000061FE00
*/
結(jié)構(gòu)體1出問(wèn)題了,具體沒(méi)搞明白,歡迎大佬指導(dǎo)。
建議可變參數(shù)使用引用頭文件stdarg.h得方式較好。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C語(yǔ)言 深入解讀數(shù)據(jù)結(jié)構(gòu)之堆的實(shí)現(xiàn)
堆就是用數(shù)組實(shí)現(xiàn)的二叉樹(shù),所以它沒(méi)有使用父指針或者子指針。堆根據(jù)“堆屬性”來(lái)排序,“堆屬性”決定了樹(shù)中節(jié)點(diǎn)的位置2021-11-11
C語(yǔ)言strlen,strcpy,strcmp,strcat,strstr字符串操作函數(shù)實(shí)現(xiàn)
這篇文章主要介紹了C語(yǔ)言strlen,strcpy,strcmp,strcat,strstr字符串操作函數(shù)實(shí)現(xiàn),,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-09-09
使用QGraphicsView實(shí)現(xiàn)氣泡聊天窗口+排雷功能
這篇文章主要介紹了使用QGraphicsView實(shí)現(xiàn)氣泡聊天窗口+排雷,重點(diǎn)給大家介紹使用QWebEngineView控件內(nèi)嵌html+CSS的實(shí)現(xiàn)方式,需要的朋友可以參考下2022-04-04
C++實(shí)現(xiàn)LeetCode(768.可排序的最大塊數(shù)之二)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(768.可排序的最大塊數(shù)之二),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
C程序函數(shù)調(diào)用&系統(tǒng)調(diào)用
這篇文章主要介紹了C程序函數(shù)調(diào)用&系統(tǒng)調(diào)用,需要的朋友可以參考下2016-09-09
關(guān)于vector迭代器失效的幾種情況總結(jié)
下面小編就為大家?guī)?lái)一篇關(guān)于vector迭代器失效的幾種情況總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
C++利用std::forward_list查找插入數(shù)據(jù)方法示例
這篇文章主要給大家介紹了關(guān)于C++利用std::forward_list查找插入數(shù)據(jù)的相關(guān)資料,文中先對(duì)std::forward_list進(jìn)行了詳細(xì)的介紹,而后通過(guò)示例代碼給大家介紹了查找的方法,需要的朋友可以參考借鑒,下面話不多說(shuō)了,來(lái)一起看看吧。2017-08-08

