函数参数如何存储在内存中?
How are function arguments stored in memory?
在尝试为可变参数函数创建自己的 stdarg.h 宏替代方案时,a.k.a。具有未知数量参数的函数,我试图了解参数在内存中的存储方式。
这是一个 MWE :
#include <stdio.h>
void foo(int num, int bar1, int bar2)
{
printf("%p %p %p %p\n", &foo, &num, &bar1, &bar2);
}
int main ()
{
int i, j;
i = 3;
j = -5;
foo(2, i, j);
return 0;
}
我毫无疑问地理解函数地址与参数地址不在同一个地方。
但后者并不总是以相同的方式组织。
在 x86_32 架构 (mingw32) 上,我得到这样的结果:
004013B0 0028FEF0 0028FEF4 0028FEF8
这意味着地址与参数的顺序相同。
但是当我 运行 它在 x86_64 上时,这次输出是:
0x400536 0x7fff53b5f03c 0x7fff53b5f038 0x7fff53b5f034
地址明显是倒序的w.r.t。参数。
因此我的问题是 (tl;dr) :
参数的地址是架构相关的,还是编译器相关的?
他们 ABI 依赖。在无关紧要的情况下(仅以已知方式调用的函数),它完全取决于编译器,这通常意味着使用没有地址的寄存器(如果您要求,这些参数将具有地址那个地址,给人一种一切都有地址的感觉)。被内联的函数甚至不再真正有参数,所以它们的地址是什么的问题是没有实际意义的——尽管当你强制发生时它们似乎存在并且有一个地址。
它依赖于编译器。编译器供应商自然要遵守 CPU 架构的规则。编译器通常也遵循平台 ABI,至少对于可能与另一个编译器生成的代码进行互操作的代码是这样。平台 ABI 是针对给定平台的调用约定、链接语义等规范。
例如linux 和其他类似 unix 的操作系统上的编译器遵守 System V Application Binary Interface, and you'll find in chapter 3.2.3 how parameters are passed to functions (arguments passed in registers are passed left to right and arguments passed in memory(on the stack) are passed from right to left). On Windows, the rules are documented here.
在 x86_64 上,您会以 "weird" 顺序获取参数,因为它们实际上根本没有传递给任何内存中的函数。它们在 cpu 寄存器中传递。通过获取它们的地址,您实际上强制编译器生成将参数存储在内存中(在您的情况下在堆栈上)的代码,以便您可以获取它们的地址。
如果不与编译器交互,就无法实现 stdarg 宏。在 gcc 中,stdarg 宏只是包装了一个内置构造,因为您无法在需要时知道参数可能在哪里(编译器可能已将寄存器重新用于某些东西)。 gcc 中的内置 stdarg 支持可以显着改变使用它们的函数的代码生成,以便参数完全可用。我认为其他编译器也是如此。
参数可能根本不存储在内存中,而是通过寄存器传递;但是该语言要求为 &
的任何符号操作数返回一个地址,因此您的观察可能是您实际尝试观察的结果,并且编译器只是将值复制到这些地址以便它们可寻址.
如果您请求地址的顺序与传递地址的顺序不同,看看会发生什么可能会很有趣,例如:
printf("%p %p %p %p\n", &num, &bar1, &bar2, &foo) ;
您可能会或可能不会得到相同的结果;关键是您观察到的地址可能是观察的产物,而不是过去的产物。当然在 ARM ABI 中,函数的前四个参数在寄存器 R0、R1、R2 和 R3 中传递,然后通过堆栈传递。
在尝试为可变参数函数创建自己的 stdarg.h 宏替代方案时,a.k.a。具有未知数量参数的函数,我试图了解参数在内存中的存储方式。
这是一个 MWE :
#include <stdio.h>
void foo(int num, int bar1, int bar2)
{
printf("%p %p %p %p\n", &foo, &num, &bar1, &bar2);
}
int main ()
{
int i, j;
i = 3;
j = -5;
foo(2, i, j);
return 0;
}
我毫无疑问地理解函数地址与参数地址不在同一个地方。 但后者并不总是以相同的方式组织。
在 x86_32 架构 (mingw32) 上,我得到这样的结果:
004013B0 0028FEF0 0028FEF4 0028FEF8
这意味着地址与参数的顺序相同。
但是当我 运行 它在 x86_64 上时,这次输出是:
0x400536 0x7fff53b5f03c 0x7fff53b5f038 0x7fff53b5f034
地址明显是倒序的w.r.t。参数。
因此我的问题是 (tl;dr) :
参数的地址是架构相关的,还是编译器相关的?
他们 ABI 依赖。在无关紧要的情况下(仅以已知方式调用的函数),它完全取决于编译器,这通常意味着使用没有地址的寄存器(如果您要求,这些参数将具有地址那个地址,给人一种一切都有地址的感觉)。被内联的函数甚至不再真正有参数,所以它们的地址是什么的问题是没有实际意义的——尽管当你强制发生时它们似乎存在并且有一个地址。
它依赖于编译器。编译器供应商自然要遵守 CPU 架构的规则。编译器通常也遵循平台 ABI,至少对于可能与另一个编译器生成的代码进行互操作的代码是这样。平台 ABI 是针对给定平台的调用约定、链接语义等规范。
例如linux 和其他类似 unix 的操作系统上的编译器遵守 System V Application Binary Interface, and you'll find in chapter 3.2.3 how parameters are passed to functions (arguments passed in registers are passed left to right and arguments passed in memory(on the stack) are passed from right to left). On Windows, the rules are documented here.
在 x86_64 上,您会以 "weird" 顺序获取参数,因为它们实际上根本没有传递给任何内存中的函数。它们在 cpu 寄存器中传递。通过获取它们的地址,您实际上强制编译器生成将参数存储在内存中(在您的情况下在堆栈上)的代码,以便您可以获取它们的地址。
如果不与编译器交互,就无法实现 stdarg 宏。在 gcc 中,stdarg 宏只是包装了一个内置构造,因为您无法在需要时知道参数可能在哪里(编译器可能已将寄存器重新用于某些东西)。 gcc 中的内置 stdarg 支持可以显着改变使用它们的函数的代码生成,以便参数完全可用。我认为其他编译器也是如此。
参数可能根本不存储在内存中,而是通过寄存器传递;但是该语言要求为 &
的任何符号操作数返回一个地址,因此您的观察可能是您实际尝试观察的结果,并且编译器只是将值复制到这些地址以便它们可寻址.
如果您请求地址的顺序与传递地址的顺序不同,看看会发生什么可能会很有趣,例如:
printf("%p %p %p %p\n", &num, &bar1, &bar2, &foo) ;
您可能会或可能不会得到相同的结果;关键是您观察到的地址可能是观察的产物,而不是过去的产物。当然在 ARM ABI 中,函数的前四个参数在寄存器 R0、R1、R2 和 R3 中传递,然后通过堆栈传递。