【问题标题】:Is it safe to pass a parameter to another function by reference (in C)?通过引用(在 C 中)将参数传递给另一个函数是否安全?
【发布时间】:2021-07-16 01:50:03
【问题描述】:

以下代码安全且可移植吗?

void funcB(int *pA)
{
    // Do something w/ pA
} 
void funcA(int a)
{
    funcB(&a);
    // Do something else w/ a
}

我的理解是,在许多系统上,函数参数被放置在 CPU 寄存器中,而不是堆栈中。因此,您实际上是将寄存器的地址传递给函数,然后将其内容压入堆栈,以便寄存器随后可用于funcB 的参数。因此,实际上,当您在 funcB 中取消引用 pA 时,您将获得地址 pA,而不是来自 funcAa 的内容。我的推理是正确的还是我完全脱离了基础?

【问题讨论】:

  • 你心目中的系统是什么?你是如何编译代码的?
  • 这就是定义的所有实现,而不是您应该担心的事情。它将与平台不同。而且您的代码是合法的,因此无需担心。对 (*pA) 所做的更改将在 funcA 中看到
  • C中你只有指针传递,与C++相反,它有指针和引用,而java只有引用(java中的所有对象都是通过引用传递的)。

标签: c


【解决方案1】:

是的,根据 C 标准完全安全且定义明确。函数参数是一个对象,并且(概念上)它有一个地址。 funcB 通过指针访问该对象是安全的。请记住,与funcA 中的任何其他局部变量一样,它的生命周期在funcA 返回时结束,因此funcB 应该小心存储指针pA 以供以后使用。

在底层,即使a 在寄存器中传递给funcA,如果需要它的地址,那么编译器将需要将该寄存器复制到堆栈中(称为“溢出”),传递该堆栈位置,并使用该地址中的值对a 进行进一步操作。 (或者,生成行为“好像”已经完成的代码。)

非优化编译器通常总是将参数溢出到堆栈中,因此无论是否需要它都真正具有地址。但如果不需要该地址,优化器可以将其去掉,并将参数保留在其寄存器中。

【讨论】:

  • 说得好。 . . .
【解决方案2】:

编译器将为您处理寄存器分配,并确保&aa 中数据的地址,而不是一些随机数据。此外,“寄存器的地址”没有多大意义,因为寄存器没有地址。事实上,表达式&a 会告诉编译器你希望a 是有地址的东西,所以它会将a 中的数据放入内存,然后获取该数据的地址。

一个例子

这就是Clang generates for this C code:

funcB:
        sub     sp, sp, #4
        str     r0, [sp]
        add     sp, sp, #4
        bx      lr
funcA:
        push    {r11, lr}
        mov     r11, sp
        sub     sp, sp, #8

        ; The argument comes in register r0,
        ; but the compiler stores it into memory
        str     r0, [sp, #4]
        ; Then it puts the address of `a` (sp + 4)
        ; into `r0` and passes that to the other function
        add     r0, sp, #4
        bl      funcB
        mov     sp, r11
        pop     {r11, lr}
        bx      lr

【讨论】:

  • 关于“另外,“寄存器的地址”没有多大意义,因为寄存器没有地址”:这取决于实现。我相信有内​​存映射寄存器的计算机。
  • @EricPostpischil,是的,我认为即使在 x86 中也存在内存映射的隐藏寄存器,但我说的是,有点像 x86 和 ARM 中的“常规”寄存器跨度>
  • 例如,Atmel AVR 具有“常规”寄存器 (R0-R31),它们也映射到 32 个最低地址。
【解决方案3】:

除非您使用损坏的编译器,否则它应该是安全的。编译器将在堆栈上分配一些区域(通常)并传递其地址,看到需要一个地址。

这是 Compiler Explorer 的一个示例。 我添加了一个函数来“做某事”以防止事情被优化。

void do_something(int*);

void funcB(int *pA)
{
    // Do something w/ pA
    do_something(pA);
} 
void funcA(int a)
{
    funcB(&a);
    // Do something else w/ a
}

这里是a result without optimization

funcB:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movq    %rdi, -8(%rbp)
        movq    -8(%rbp), %rax
        movq    %rax, %rdi
        call    do_something
        nop
        leave
        ret
funcA:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        leaq    -4(%rbp), %rax
        movq    %rax, %rdi
        call    funcB
        nop
        leave
        ret

这里是a result with -O2 optimization

funcB:
        jmp     do_something
funcA:
        subq    $24, %rsp
        movl    %edi, 12(%rsp)
        leaq    12(%rsp), %rdi
        call    do_something
        addq    $24, %rsp
        ret

现在您可以看到funcA (%edi) 的参数存储在堆栈中,并且在这两种情况下,它的地址都传递给了funcB

【讨论】:

    猜你喜欢
    • 2017-05-19
    • 2011-11-25
    • 1970-01-01
    • 1970-01-01
    • 2014-05-05
    • 1970-01-01
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多