如何解决函数在C中返回局部变量的地址时为NULL行为
我有以下C代码:
interface ICovariant<out R>
{
// The following statement generates a compiler error
// because you can use only contravariant or invariant types
// in generic constraints.
// void DoSomething<T>() where T : R;
}
如果我使用gcc进行编译并运行可执行文件,则会得到#include <stdlib.h>
#include <stdio.h>
char* foo() {
char abc[4] = "abc";
return abc;
}
int main() {
printf("%s",foo());
return 0;
}
作为输出。
如果我运行稍微修改的代码:
(null)%
我遇到了细分错误。
我的问题是:为什么我的第一个代码不会出现分段错误?我正在运行Linux和gcc版本:gcc(Ubuntu 7.4.0-1ubuntu1〜18.04.1)7.4.0
这两个代码在编译时都会产生#include <stdlib.h>
#include <stdio.h>
char* foo() {
char abc[4] = "abc";
return abc;
}
int main() {
printf("%c",*(foo()));
return 0;
}
警告
解决方法
return abc;
开始执行时,abc
是指向foo
内部定义的数组的指针。 (通常,它指定数组本身,但会自动转换为第一个元素的地址。)该函数将返回此指针值。但是,当函数执行结束时,数组的生存期结束。
每C 2018 6.2.4 2:
当指针指向的对象(或刚刚过去的对象)达到其生命周期的尽头时,指针的值将变得不确定。
当值在C中为不确定时,它的行为就好像它具有任何值一样,包括每次尝试使用该值时都有不同的值或包含陷阱值(C 2018 3.19。 2和3.19.3)。请注意,这不仅意味着指针值所指向的内容是不确定的;还包括以下内容:指针本身的值是不确定的。
因此,即使abc
在内存中有某个地址(例如100400),也并不意味着100400将返回给调用方。返回给调用方的值是不确定的:它可以是任何值,包括空指针值。
您的编译器优化器似乎已通过提供或允许将空指针值作为函数foo
的返回值来响应代码中未定义的行为。 C标准允许这样做。
当您将此空指针传递给printf
以便与%s
一起使用时,您的printf
实现会检查该指针,发现它是一个空指针,而是打印“(null)”尝试使用它来访问内存中的字符串的方法。
当您尝试使用*(foo())
取消引用指针时,没有对指针值进行初步检查。该程序的机器代码尝试使用空指针访问内存,这导致了段错误。
由于要创建局部变量abc,因此该变量仅在函数foo的范围内有效。从foo返回后,返回该变量的地址就没有意义了,该地址将不再有效。
还请记住,C使用堆栈将参数传递给函数并从函数值中返回。同样,局部变量也在堆栈中创建,将通过函数调用机制进行修改,因此使用该地址最终将破坏堆栈。
要创建指针,您应该使用堆分配(使用malloc系列函数),或者必须确保在使用变量时变量已位于现有范围内。
当您尝试取消引用指向局部变量的指针时,第二个代码将调用未定义的行为。现在,此局部变量在其范围之外不存在。因此,内存无效
在第一个代码中,您尝试访问范围之外的局部变量。现在,在这种情况下,函数应该返回一个char *
。返回本地变量时,得到的是空打印,不会引起分段错误。
请考虑以下事件顺序:
- 您入住酒店,然后被安置在137房间。
- 您告诉前台打电话给您的朋友,并邀请他们明天早上2点在您的房间参加野舞派对。
- 但是,您的预订要等到明天凌晨2点。也许还有其他客人的预订。也许房间会空着。谁知道。也许前台的人都知道。也许他们没有。
那么前台应该怎么做?
他们仍然可以发送指示137房间的邀请,也许不知道那时您不会在那里,因为他们忘记了检查预订记录。也许他们只是不在乎。
或者他们可以拒绝发送邀请并告诉您。
或者他们可能只是忽略您的请求,不发送任何内容,也不告诉任何人。
或者他们可以发送和邀请,但要注明虚假的房间号。也许他们事先准备了邀请函空白,他们只需要填写时间和房间号即可。但是,由于技术上的先进,他们不会填写房间号,如果他们知道不为该特定客人预订的话,便会发送一个带有默认房间号的房间,也许是零?
也许我们将来住的话,他们甚至可能会发送带有137房间的电子邀请函,这将在您从酒店退房时自动销毁!
无论他们做什么,他们都无法发送指示正确房间号的邀请,因为没有正确的房间号。您不会有任何房间号码。因此,他们会做任何事情。他们可能总是选择一种策略来应对这种情况。否则他们可能会掷硬币。也许不同的工作人员会做不同的事情。谁知道。
因此,他们的某些策略会引起严重的崩溃(您的朋友在错误的时间醒来一个错误的客人,他们报警了,一切都不好了。
其他策略将产生不太明显的结果。拒绝继续,让您知道吗?让您知道出了点问题,但是还是继续吗?忽略危险指示?用不太危险的说明代替它?所有这些事情都是可能的。
这对应于当您指示编译器执行明显危险和非法的操作时,编译器可能会执行的操作。忽略危险,或者拒绝继续发送诊断消息,或者产生诊断消息,无论如何继续执行,或者完全跳过危险说明(但前提是必须100%确认即将销毁),或者稍微对其进行调整,以便危险性较小。真正的编译器实际上是在不同情况下完成所有这些操作。重要的是要知道,向编译器询问不可能的事情并不总是导致程序实际上尝试执行不可能的事情。
此答案部分基于其作者删除的答案https://stackoverflow.com/a/63862176/775806。
,取消引用不存在的对象是未定义的行为。
为什么首先起作用:我的猜测是因为编译器优化了对该函数的调用。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。