gpt4 book ai didi

c - 函数返回局部变量的地址,但仍在c中编译,为什么?

转载 作者:行者123 更新时间:2023-12-02 11:15:49 27 4
gpt4 key购买 nike

即使我收到警告,函数也会从局部变量返回地址,它也会进行编译。那不是编译器的UB吗?生成的程序集:

    .text
.LC0:
.asciz "%i\n"
.globl foo
.type foo, @function
foo:
pushq %rbp #
movq %rsp, %rbp #,
sub $16, %rsp #,
mov %rdi, -8(%rbp) #,
leaq -8(%rbp), %rax #,
# a.c:5: }
leave
ret
.size foo, .-foo
.globl main
.type main, @function
main:
pushq %rbp #
movq %rsp, %rbp #,
# a.c:8: foo();
movl $123, %edi #,
call foo #
movq (%rax), %rsi #,
leaq .LC0(%rip), %rdi #,
movl $0, %eax #,
call printf #,
movl $0, %eax
# a.c:9: }
popq %rbp #
ret
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
在这里,组装返回一个本地变量 leaq -8(%rbp), %rax的地址,但是随后它调用了insttion leave,它应该“使”该地址 -8(%rbp)无效(添加了堆栈指针,因此我应该不再能够取消引用该地址,因为程序继续进行)。那么,为什么当返回到 mov (%rax), %rdi的地址不再有效时,为什么编译并愉快地取消引用 %rax呢?是段错误还是终止?

最佳答案

Even I get an warning a function returns an address from localvariable, it compiles. Isn't it then UB of compiler?


不,但是如果是,您怎么能知道?您似乎对未定义的行为有误解。这并不意味着“编译器必须拒绝它”,“编译器必须警告它”,“程序必须终止”或任何类似的东西。这些确实可能是UB的体现,但是如果语言规范要求这种行为,那么它就不会被不确定。 确保C程序不行使未定义的行为是程序员的责任,而不是C的实现。 在程序员不履行该职责的情况下,C实现显式不承担对等责任-它可以在其能力范围内做任何事情。
而且,没有单个“the” C编译器。不同的编译器可能会做不同的事情,但仍然符合C语言规范。这是实现定义的,未指定的和未定义的行为出现的地方。C语言设计者有意允许这种差异。其中,它允许实现以对其特定目标硬件和执行环境自然的方式进行操作。
现在让我们回到“否”。这是一个返回自动变量地址的函数的典型示例:
int *foo() {
int bar = 0;
return &bar;
}
那应该具有未定义的行为呢?该函数定义良好,可以计算 bar的地址,并且所得的指针值具有该函数要返回的正确类型。当函数返回时, bar的生命周期结束后,返回值将变得不确定(标准的第6.2.4/2段),但这本身不会引起任何未定义的行为。
或考虑来电者:
void test1() {
int *bar_ptr = foo(); // OK under all circumstances
}
正如已经讨论过的,我们特定的 foo()的返回值将始终是不确定的,因此,特别地,它可能是陷阱表示。但这是运行时的考虑因素,而不是编译时的考虑因素。即使该值是陷阱表示,C也不要求实现拒绝或无法存储它。特别是,C11的脚注50在这一点上是明确的:

Thus, an automatic variable can be initialized to a traprepresentation without causing undefined behavior, but the value ofthe variable cannot be used until a proper value is stored in it.


还要注意 foo()test1()可以通过编译器的不同运行进行编译,因此,在编译 test1()时,编译器除了原型(prototype)所指示的内容以外,对 foo()的行为一无所知。 C并未对依赖于程序运行时行为的实现提出转换时间要求。
另一方面,如果对该函数进行了一些修改,则有关陷阱表示的要求将有所不同:
void test2() {
int *bar_ptr = NULL;
bar_ptr = foo(); // UB (only) if foo() returns a trap representation
}
如果 foo()的返回值证明是一个陷阱表示形式,则将其存储在 bar_ptr中(与使用它初始化 bar_ptr相反)会在运行时产生未定义的行为。但是,“未定义”再次表示其在 jar 头上所说的内容。 C没有为实现在这种情况下展现定义任何特定的行为,尤其是,它完全不需要程序终止或表现出任何外部可见的行为。同样,这是运行时的考虑因素,而不是编译时的考虑因素。
此外,如果 foo()的返回值证明不是陷阱表示(而是不是任何 Activity 对象的地址的指针值),则读取该值本身没有任何问题,或者:
void test3() {
int *bar_ptr = foo();
// UB (only) if foo() returned a trap representation:
printf("foo() returned %p\n", (void *) bar_ptr);
}
此区域中最大和最常用的未定义行为是尝试取消引用 foo()的返回值的行为,无论是否捕获表示,几乎肯定不会指向 Activity 的 int对象:
void test4() {
int *bar_ptr = foo();
// UB under all circumstances for the given foo():
printf("foo() returned a pointer to an int with value %d\n", *bar_ptr);
}
但这又是运行时的考虑因素,而不是编译时的考虑因素。同样,未定义意味着未定义。只要涉及的函数有作用域内的声明,就应该期望C实现能够成功地进行转换,并且尽管某些编译器可能会发出警告,但他们没有义务这样做。函数 test4的运行时行为未定义,但这并不意味着该程序必然会发生段错误或以其他某种方式终止。可能,但是我希望在实践中,许多实现所表现出的未定义行为将是打印“foo()返回指向值为0的int的指针”。这样做绝不违反C的要求。

关于c - 函数返回局部变量的地址,但仍在c中编译,为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63283986/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com