GCC下,C语言参数的内存分配 以及 函数可变参数

今天想看看C语言是怎么实现函数可变参数列表的,无意间一个小实验发现了C语言在gcc下编译时内存分配的策略(VC不知道怎么样),下面记录一下


现在有一段这样的小程序

#include <stdio.h>
#include <stdlib.h>

void myfunc(int a,char b,short c)
{
    printf("%x\n%x\n%x",&a,&b,&c);  //分三行输出a,b,c的地址
}
int main()
{
    int a = 4;
    char b = 'c';
    short c = 2;
    myfunc(a,b,c);
    return 0;
}

首先,我们知道C语言中函数的参数是从后往前压入栈中的,也就是先把 参数 c 压栈,然后是b,最后是a

也就是说,c 的地址要小于b的地址,b 的地址要小于 a 的地址

那么现在我假设 c 的地址是 0x28fee8,能不能算出b 和 a的地址呢?

答案当然是可以的

我们按照各个参数的大小,可以推算出 b的地址应该是 0x28fee8 + 0x2 (这是short类型的大小) = 0x28feea , 响应的a的地址应该是 0x28feea + 0x1 = 0x28feeb

实际结果是不是这样?运行一下程序发现结果是这样的:


跟我们预想的结果不一样.....错误发生在哪里?


仔细观察可以发现,三个地址间隔都是4, 现在计算机是以字节为单位来记录地址的,地址相差4,也就是4个字节,也就是32位。

一般我们的系统的内存地址是按4个字节对齐的,这样访问效率会比较高,因为地址总线总是按照对齐后的地址来访问,至于内存对齐的概念,可以Google一下。

举个例子,如果我们想得到0x00000001开始的4字节内容,系统首先需要以0x00000000读4个字节,从中去后面的3个字节,然后再用0x00000004作为开始地址,获得下一个4字节,再从中取1个字节,最后组合两次取值。这样子的效率就很低了。但是,如果我们一开始就对齐到0x00000000,系统只要一次读取4个字节就可以,效率就高很多。


回到我们的问题:

其实我们的问题和上面的内存对齐并没有非常直接的关系,只是概念差不多而已,gcc 至少会给每个参数分配4个字节的大小,比如如果是char参数,那么相应地会得到一个4字节的内存块,哪怕它实际只用到了1个字节,如果是double,那就分配8个字节


有什么好处呢?至少可以用来实现可变参数,同时保证内存读取的速度,不过响应地耗费了内存,但是这只是在栈中分配内存,而不是进行网络传输或者文件IO读写,相比而言,CPU计算速度和内存读取速度的优先级要比这几个字节的节省的优先级要高许多。

C语言在头文件stdarg.h中定义了几个宏,详细可以百度百科 va_list

其中就有一个宏 _INTSIZEOF(n)

#define  _INTSIZEOF(n)     ( ( sizeof(n) + sizeof(int) - 1 ) & ~(sizeof(int) - 1) )

这个宏定义看起来很复杂,其实含义就是,保证 _INTSIZEOF(sometype) 算出来的 sometype的大小符合下面两种情况

  • 如果 sizeof(sometype) < sizeof(int) ,与运算会使得最后的结果是 sizeof(int) ,读者不妨试试看,或者分析一下与运算表达式
  • 如果sizeof(sometype) >= sizeof(int) , 结果就直接是 比sizeof(sometype)大的最接近的 sizeof(int)的倍数
所以_INTSIZEOF(n) 返回的其实就是n的大小,只不过保证它一定是sizeof(int)的倍数,一般就是4,8,12等

这到底有什么用呢?看看 va_arg的宏定义:
#define va_arg(ap , t)   ( *(t *) ( (ap+=_INTSIZEOF(n)) - _INTSIZEOF(n) ) )
含义就是,为了获取 ap 指向的参数值,先让它加(也就是指针向前移动) _INTSIZEOF(n) , 然后再获得减去_INTSIZEOF(n) 后的结果
这样,既移动了指针,又取回了移动之前指针的指向,一石二鸟,这设计虽然简单,但却非常巧妙

这样我们就可以一个个地取回参数值了,而无论参数有多少个,函数可以从参数列表中的最后一个固定参数开始,不断地从栈顶取回参数,知道栈底,也就是参数列表中的最后一个参数

具体的例子大家可以参考 printf 函数

好了就到这。不知道说得对不对,只是我凭借小实验结果推敲出来的结论,如果有误,欢迎各位指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值