写这篇笔记是因为对C语言函数参数的传递突然有了很多疑惑。ATPCS规定R0~R4用于存储前四个参数,那么如果对前四个参数取址得到的是什么结果呢?CPU的寄存器是自有的,不在内存空间中。如果要实现变参函数,找不到参数的入口地址,那接下来的工作就无法完成。于是写了一些代码,做了一些实验来寻找答案。代码在ARM和MSP430芯片上通过运行。
结论
(1) 参数数量固定, 并且参数数量小于等于4个.
参数会从左到右传到R0~R3寄存器中, 同时在内存中会有这些参数的副本, 并且参数按从左到右依次占据内存从高地址到低地址的连续空间.
(2) 参数数量固定, 并且参数数量大于等于5个
前四个参数的行为同(1), 从第5个参数到后面所有参数, 按从左到右依次占据内存从低址到高地址的连续空间.
(3) 参数数量不固定
原则: 最后一个固定参数的相邻高地址空间开始存放变参, 并且变参按从左到右依次占据内从从低地址到高地址的连续空间.
对于其余固定参数, 优先传到R0~R3, 而内存中也会存有这些参数的副本, 从高地址到低地址连续存储; 当R0~R3不够用时, 继续将参数从低地址到高地址存储到内存中, 直到和最后一个固定参数相邻.
P.S. 在函数中获取参数的地址, 得到的是其在内存中的地址, 对于需要传进R0~R3的参数, 则是其在内存中副本的地址.
实验
(一) 使用arm-linux-gnueabihf-gcc编译, 在imx6ull上运行. IMX6ULL是32位单片机, int类型数据占4个字节, 指针类型占4个字节.
#include "stdio.h"
int func_va_list(int a, int b, int c, int d, int e, int f, ...){
int *p;
p = (int*)&a;
printf("&a = 0x%8x, p= 0x%8x, p_a = %d, a = %d\n", &a, p, *p, a);
p--;printf("p--\n");
printf("&b = 0x%8x, p= 0x%8x, p_b = %d, b = %d\n", &b, p, *p, b);
p--;
printf("&c = 0x%8x, p= 0x%8x, p_c = %d, c = %d\n", &c, p, *p, c);
p--;
printf("&d = 0x%8x, p= 0x%8x, p_d = %d, d = %d\n", &d, p, *p, d);
p--;
printf("&e = 0x%8x, p= 0x%8x, p_e = %d, e = %d\n", &e, p, *p, e);
p--;
printf("&f = 0x%8x, p= 0x%8x, p_f = %d, f = %d\n", &f, p, *p, f);
p--;
printf("p= 0x%8x, p_g = %d\n", p, *p);
return a+f;
}
int func_va_list2(int a, ...){
int *p;
p = (int*)&a;
printf("p= 0x%8x, p_a = %d\n", p, *p);
p++;printf("p++\n");
printf("p= 0x%8x, p_b = %d\n", p, *p);
p++;
printf("p= 0x%8x, p_c = %d\n", p, *p);
p++;
printf("p= 0x%8x, p_d = %d\n", p, *p);
p++;
printf("p= 0x%8x, p_e = %d\n", p, *p);
p++;
printf("p= 0x%8x, p_f = %d\n", p, *p);
p++;
printf("p= 0x%8x, p_g = %d\n", p, *p);
return a+(*p);
}
int func_va_list3(int a, int b, ...){
int *p;
p = (int*)&a;
printf("p= 0x%8x, p_a = %d, &a = 0x%8x\n", p, *p, &a);
p++;printf("p++\n");
printf("p= 0x%8x, p_b = %d, &b = 0x%8x\n", p, *p, &b);