我需要了解这些 C 指针的行为



玩指针时,我产生了一种我无法解释的行为。当我在附件函数中打印字符串时,我在打印未初始化的 char* 时在我的主线程中出现段错误(我认为这是合理的),棘手的部分是如果我以相同的方式执行其他所有操作并且不使用附件函数中的 printf 行,程序运行完美,以至于它令人惊讶地隐式返回堆栈字符串的地址。

这是我编写并运行它的程序(在 Fedora 35 上安装为原版)。即使您不能完全解释这个问题,也非常欢迎任何帮助理解这一点。

#include<stdio.h>
#include <stdlib.h>
#include<string.h>
/* This function generates a stack string and fills it */
char* generate_stack_string()
{
char stack_string[70];
strcpy(stack_string,"test");

/*the next line is the one that when added crashes the program*/

//printf("adress ( %p ) and text ( %s ) of stack string inside the functionnn",stack_string, stack_string);


//function compiles and runs normally despite no return value, but seems to return something anyhow
}
int main()
{   char *pointer_in_main;  
//this pointer is never malloced, this is intentional, to provoke the behaviour

printf("pointer_in_main adress before function call  %pnn",pointer_in_main);
pointer_in_main = generate_stack_string();
printf("pointer_in_main adress after function call  %pnn",pointer_in_main);

//This is where program crashses when the heap string is printed
printf("pointer_in_main content after function call %snn",pointer_in_main);

}

理解这都是未定义的行为后,让我们看一下您的函数:

char* generate_stack_string()
{
char stack_string[70];
strcpy(stack_string,"test");

/*the next line is the one that when added crashes the program*/

//printf("adress ( %p ) and text ( %s ) of stack string inside the functionnn",stack_string, stack_string);


//function compiles and runs normally despite no return value, but seems to return something anyhow
}

此函数不返回值,尽管它已声明这样做。 从函数返回值通常是通过将值放在寄存器中来执行的。 由于没有return语句,因此相关寄存器中碰巧出现的任何值都将是返回的值。 由于函数中的最后一条语句也是函数调用,因此寄存器中的值是从函数返回的值。 在strcpy的情况下,这恰好是第一个参数的值,即stack_string转换为指针。 因此,幸运的是,您正在返回您打算返回的指针。

运行此代码,我得到以下输出:

pointer_in_main adress before function call  (nil)
pointer_in_main adress after function call  0x7ffe3c7cd3e0
pointer_in_main content after function call test

您也很"幸运",当最后一次调用printf时,stack_string以前使用的内存内容没有被覆盖main

现在,如果我们取消注释generate_stack_string中的printf调用,我得到以下输出:

pointer_in_main adress before function call  (nil)
adress ( 0x7ffec0587ee0 ) and text ( test ) of stack string inside the function
pointer_in_main adress after function call  0x51
Segmentation fault (core dumped)

在这里,我们可以看到返回的值位于进程的有效地址空间之外,因此尝试取消引用它会导致段错误。 但是这个价值是什么?

查看更新的函数,执行的最后一行是对printf的调用。 此函数返回打印的字符数,0x51(十进制 81)恰好是打印的字符数。 所以这个值被留在用于从函数返回值的寄存器中,所以这就是返回的内容。

但同样,这都是未定义的行为。 使用修改后的代码,无论优化级别如何,我都会得到相同的段错误。 如果我使用-O1或更高版本运行原始代码,我会得到以下输出:

pointer_in_main adress before function call  (nil)
pointer_in_main adress after function call  (nil)
pointer_in_main content after function call (null)

因此,当您的程序具有未定义的行为时,所有赌注都将关闭。

程序中存在许多问题,其中大多数问题都会导致未定义的行为。例如,指针pointer_in_main初始化,当您编写以下内容时,您在调用printf时使用此指针:

printf("pointer_in_main adress before function call  %pnn",pointer_in_main);//this is undefined behavior because pointer_in_main is uninitialized
//whatever happens after this is not reliable

在上面的printf调用中,您使用的是未初始化的指针pointer_in_main,这会导致未定义的行为

未定义的行为意味着可能发生任何1的情况,包括但不限于提供预期输出的程序。但永远不要依赖(或基于)具有未定义行为的程序的输出得出结论。

因此,您看到(可能看到)的输出是未定义行为的结果。正如我所说,不要依赖具有UB的程序的输出。程序可能只是崩溃

因此,使程序正确的第一步是删除UB。然后,只有这样,您才能开始推理程序的输出。


1有关未定义行为的技术上更准确的定义,请参阅此处提到的内容:对程序的行为没有限制

对于初学者来说,这个声明

printf("pointer_in_main adress before function call  %pnn",pointer_in_main);

具有未定义的行为,因为指针未初始化且具有不确定的值。

char *pointer_in_main;  

此函数不返回任何内容

char* generate_heap_string()
{
char heap_string[70];
strcpy(heap_string,"test");

/*the next line is the one that when added crashes the program*/

//printf("adress ( %p ) and text ( %s ) of heap string inside the functionnn",heap_string,heap_string);


//function compiles and runs normally despite no return value, but seems to return something anyhow
}

所以这些 printf 的呼唤再次

出现
pointer_in_main = generate_heap_string();
printf("pointer_in_main adress after function call  %pnn",pointer_in_main);
//This is where program crashses when the heap string is printed
printf("pointer_in_main content after function call %snn",pointer_in_main);

调用未定义的行为。

即使您将按以下方式更改功能

char* generate_heap_string()
{
char heap_string[70];
strcpy(heap_string,"test");

/*the next line is the one that when added crashes the program*/

//printf("adress ( %p ) and text ( %s ) of heap string inside the functionnn",heap_string,heap_string);

return heap_string;    
}

那么尽管如此,这个 printf 的呼吁

pointer_in_main = generate_heap_string();
//...
//This is where program crashses when the heap string is printed
printf("pointer_in_main content after function call %snn",pointer_in_main);

将再次调用未定义的行为,因为在函数中声明的本地数组在退出函数后将不活动,并且指针pointer_in_main的值无效。

这就是在这个电话中

//This is where program crashses when the heap string is printed
printf("pointer_in_main content after function call %snn",pointer_in_main);

有人试图取消引用无效指针。

printf 调用之间的区别在于,在一种情况下,您尝试打印指针的无效值,但在另一种情况下

//This is where program crashses when the heap string is printed
printf("pointer_in_main content after function call %snn",pointer_in_main);

您正在使用无效值来访问内存。

最新更新