我已经尝试了一些功能,我发现参数的顺序在内存中是相反的。那是为什么?
堆叠TEST.CPP:
#include <stdio.h>
void test( int a, int b, int c ) {
printf("%p %p %p\n", &a, &b, &c);
printf("%d %d\n", *(&b - 1), *(&b + 1) );
}
int main() {
test(1,2,3);
return 0;
}
铛:
$ clang++ stack-test.cpp && ./a.out
0x7fffb9bb816c 0x7fffb9bb8168 0x7fffb9bb8164
3 1
GCC
$ g++ stack-test.cpp && ./a.out
0x7ffe0b983b3c 0x7ffe0b983b38 0x7ffe0b983b34
3 1
编辑:不重复:评估顺序可能与内存布局不同,因此它是一个不同的问题。
答案 0 :(得分:8)
此行为是特定于实现的。
在你的情况下,这是因为参数被推到了堆栈上。这里有一个interesting article,它显示了进程的典型内存布局,显示了堆栈如何减少。因此,在堆栈上推送的第一个参数将具有最高地址。
答案 1 :(得分:7)
调用约定取决于实现。
但是为了支持C变量函数(在形式参数列表中用...
椭圆表示的C ++中),通常按从右到左的顺序推送参数,或者为它们保留堆栈空间。这通常称为(1) C调用约定。根据这个约定,以及机器堆栈在内存中向下增长的常见约定,第一个参数应该以最低地址结束,与结果相反。
当我使用MinGW g ++ 5.1(64位)编译程序时,我得到了
000000000023FE30 000000000023FE38 000000000023FE40
当我使用32位Visual C ++ 2015编译程序时,我得到了
00BFFC5C 00BFFC60 00BFFC64
这两个结果都与C调用约定一致,与您的结果相反。
所以结论似乎是你的编译器默认不是C调用约定,至少对于非可变函数。
您可以通过在正式参数列表的末尾添加...
来测试这一点。
1) C调用约定还包括调用函数在函数返回时调整堆栈指针,但这与此无关。 功能
答案 2 :(得分:2)
C(和C ++)标准没有定义传递的参数的顺序,或者它们应该如何在内存中组织。编译器开发人员(通常与操作系统开发人员合作)可以提供适用于特定处理器体系结构的东西。
在MOST体系结构中,堆栈(和寄存器)用于将参数传递给函数,同样,对于MOST体系结构,堆栈从&#34;高到低&#34;地址,并且在大多数C实现中,传递的参数的顺序是&#34;最后一个&#34;,所以如果我们有一个函数
void test( int a, int b, int c )
然后按顺序传递参数:
c, b, a
到函数。
然而,使参数的值在寄存器中传递,并且使用参数的代码获取这些参数的地址 - 寄存器不具有地址,因此您无法解决这个问题。获取寄存器变量的地址。因此编译器将生成一些代码来将堆栈中的地址[从我们可以获取值的地址]本地存储到函数中。这完全取决于编译器的决定,它命令它这样做,我相当肯定这就是你所看到的。
如果我们接受你的代码并通过clang传递它,我们会看到:
define void @test(i32 %a, i32 %b, i32 %c) #0 {
entry:
%a.addr = alloca i32, align 4
%b.addr = alloca i32, align 4
%c.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
store i32 %b, i32* %b.addr, align 4
store i32 %c, i32* %c.addr, align 4
%call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.str, i32 0, i32 0), i32* %a.addr, i32* %b.addr, i32* %c.addr)
%add.ptr = getelementptr inbounds i32, i32* %b.addr, i64 -1
%0 = load i32, i32* %add.ptr, align 4
%add.ptr1 = getelementptr inbounds i32, i32* %b.addr, i64 1
%1 = load i32, i32* %add.ptr1, align 4
%call2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.1, i32 0, i32 0), i32 %0, i32 %1)
ret void
}
虽然阅读起来可能不是很简单,但你可以看到测试函数的前几行是:
%a.addr = alloca i32, align 4
%b.addr = alloca i32, align 4
%c.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
store i32 %b, i32* %b.addr, align 4
store i32 %c, i32* %c.addr, align 4
这实际上是在堆栈上创建空间(%alloca
)并将变量a
,b
和c
存储到这些位置。
更难以阅读的是gcc生成的汇编代码,但你可以看到类似的事情发生在这里:
subq $16, %rsp ; <-- "alloca" for 4 integers.
movl %edi, -4(%rbp) ; Store a, b and c.
movl %esi, -8(%rbp)
movl %edx, -12(%rbp)
leaq -12(%rbp), %rcx ; Take address of ...
leaq -8(%rbp), %rdx
leaq -4(%rbp), %rax
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf ; Call printf.
您可能想知道为什么它为4个整数分配空间 - 这是因为堆栈应始终在x86-64中与16个字节对齐。
答案 3 :(得分:1)
C(和C ++)代码使用处理器堆栈将参数传递给函数。
堆栈的运行方式取决于处理器。堆栈可以(理论上)向下或向上增长。因此,如果地址增大或缩小,您的处理器就会定义。最后,并不是单独的处理器体系结构对此负责,但在体系结构上运行的代码有calling conventions。
调用约定说,如何将参数放在一个特定处理器体系结构的堆栈上。这些约定是必要的,来自不同编译器的库可以链接在一起。
基本上,对于你作为C用户,如果堆栈中变量的地址增大或缩小,它通常没有区别。
<强>详细信息:强>
答案 4 :(得分:1)
ABI定义了如何传递参数。
在你的例子中,由于gcc和clang的x86_64 ABI默认值在寄存器(*)上传递了参数,因此它有点复杂,没有地址。
然后引用参数,因此编译器被迫为这些变量分配本地存储,并且排序和内存布局也是特定于实现的。
答案 5 :(得分:0)
谈到32位x86 Windows
简短回答:指向函数参数的指针不是指向实际函数调用推送的堆栈的指针,但可以是编译器重定位变量的任何位置。
答案很长: 在将我的代码从bcc32(Embarcadero经典编译器)转换为CLANG时遇到了同样的问题。 MIDL编译器生成的RPC代码被破坏,因为RPC函数参数通过将指针指向第一个函数参数来转换序列化参数,假设所有后面的参数将遵循例如序列化(&安培;一个)
BCC32和CLANG生成的调试cdecl函数调用:
BCC32 :函数参数在堆栈上以正确的顺序传递,然后当需要参数地址时,直接给出堆栈地址。
CLANG :函数参数在堆栈上以正确的顺序传递,但是在实际函数中,所有参数的副本都在内存中以堆栈的相反顺序进行,并且函数参数地址是必需的,内存地址是直接给出的,导致恢复顺序。
否则说,不要假设函数C / C ++代码中的函数参数如何处理在内存中。它依赖于编译器。
在我的例子中,一个可能的解决方案是使用pascal调用约定(Win32)声明RPC函数,强制MIDL编译器单独解析参数。不幸的是,MIDL生成的代码很繁重而且代码需要进行大量的调整才能编译,但仍未完成。