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

