将数据复制/扫描/读取到未初始化的指针时发生崩溃或“分段错误”

Posted

技术标签:

【中文标题】将数据复制/扫描/读取到未初始化的指针时发生崩溃或“分段错误”【英文标题】:Crash or "segmentation fault" when data is copied/scanned/read to an uninitialized pointer 【发布时间】:2016-09-29 16:42:17 【问题描述】:

此问题旨在作为所有常见问题的参考:

当我将数据复制/扫描到未初始化指针指向的地址时,为什么会出现神秘的崩溃或“分段错误”?

例如:

char* ptr;
strcpy(ptr, "hello world"); // crash here!

char* ptr;
scanf("%s", ptr); // crash here!

【问题讨论】:

问题更多的是 OP 甚至不知道指针未初始化,但是一旦您声明/定义(他们也混淆了)指针,就会神奇地出现一个对象。 如果您的目标是让遇到此问题的人在此处发布此问题之前阅读此问题,您可能应该更改标题。 @Olaf 确实如此,那么您将他们的细分问题作为重复项投票,并附上指向该问题的链接。我一直错过这样的常见问题解答;终于有时间写一篇了。 @barakmanos 目的是将这篇文章用作常见问题的“规范副本”。我真的不希望新手自己找到它。 @Lundin:感谢您的努力。如果这意味着作为一个 dup-CV,我和你在一起。但实际上我更希望他们在发布之前自己找到它。但无论如何,这可能是一厢情愿的想法,因为初学者倾向于认为他们的问题是独一无二的。所以,请 +1,我会记住的 - 谢谢! 【参考方案1】:

学习C时经常出现的一种情况是尝试使用单引号来表示字符串文字:

char ptr[5];
strcpy(ptr, 'hello'); // crash here!
//            ^     ^   because of ' instead of "

在 C 中,'h' 是单字符文字,而 "h" 是包含 'h' 和空终止符 \0 的字符串文字(即 2 字符数组)。另外,在C语言中,字符文字的类型是int,即sizeof('h')等价于sizeof(int),而sizeof(char)1

char h = 'h';
printf("Size: %zu\n", sizeof(h));     // Size: 1
printf("Size: %zu\n", sizeof('h'));   // likely output: Size: 4

【讨论】:

【参考方案2】:

为了制作字符串的可修改副本,而不是使用 mallocstrlenstrcpy,POSIX C 库在 <string.h> 中有一个名为 strdup 的便捷函数,它将返回传入的以空字符结尾的字符串,并分配了存储持续时间。使用后应该用free释放指针:

char* ptr;
ptr = strdup("hello world");
ptr[0] = 'H';
puts(ptr);
free(ptr);

【讨论】:

请注意,strdup 不是标准 C 语言,也不一定是可移植的。 @Lundin 它符合ISO 标准,但不符合C 标准。至于可移植性,这就是 POSIX 中的 P 所代表的含义。 当您不在 PC 环境中工作时,这无关紧要。 @AnttiHaapala 确实如此。 Unix 程序员对“可移植”的定义通常意味着“可移植到另一个 Unix”。 @MikeHousky POSIX 仍然是一个真正的标准,甚至微软曾经也很想实施的标准,但效果不佳【参考方案3】:

指针是一种特殊类型的变量,它只能包含另一个变量的地址。它不能包含任何数据。您不能“将数据复制/存储到指针中”——这没有任何意义。您只能设置一个指针来指向其他地方分配的数据。

这意味着为了使指针有意义,它必须始终指向有效的内存位置。例如,它可以指向堆栈上分配的内存:


  int data = 0;
  int* ptr = &data;
  ...

或者在堆上动态分配内存:

int* ptr = malloc(sizeof(int));

在初始化之前使用指针总是一个错误。它还没有指向有效的内存。

这些示例都可能导致程序崩溃或其他类型的意外行为,例如“分段错误”:

/*** examples of incorrect use of pointers ***/

// 1.
int* bad;
*bad = 42;

// 2.
char* bad;
strcpy(bad, "hello");

相反,您必须确保指针指向(足够)分配的内存:

/*** examples of correct use of pointers ***/

// 1.
int var;
int* good = &var;
*good = 42;

// 2.
char* good = malloc(5 + 1); // allocates memory for 5 characters *and*  the null terminator
strcpy(good, "hello");

请注意,您还可以设置一个指向明确定义的“无处”的指针,方法是让它指向NULL。这使它成为一个空指针,它是一个保证不指向任何有效内存的指针。这与让指针完全未初始化不同。

int* p1 = NULL; // pointer to nowhere
int* p2;        // uninitialized pointer, pointer to "anywhere", cannot be used yet

然而,如果您尝试访问由空指针指向的内存,您可能会遇到与使用未初始化指针时类似的问题:崩溃或分段错误。在最好的情况下,您的系统会注意到您正在尝试访问地址 null,然后引发“空指针异常”。

空指针异常bug的解决方法是一样的:使用前必须先设置指针指向有效内存。


延伸阅读:

指向无效数据的指针How to access a local variable from a different function using pointers?Can a local variable's memory be accessed outside its scope?

分段错误及原因What is a segmentation fault?Why do I get a segmentation fault when writing to a string initialized with "char *s" but not "char s[]"?What is the difference between char s[] and char *s?Definitive List of Common Reasons for Segmentation FaultsWhat is a bus error?

【讨论】:

这类错误通常是由尚未掌握指针是什么或它们如何工作的初学者编写的。所以请注意,这个社区 wiki 的目的是保持基本的解释。如果您希望留下更高级的答案并参考 C 标准等,请针对该问题发布不同的答案。 "它不能包含任何数据。" - 嗯,实际上地址它的数据。 @Olaf 请在这里保持基本的东西:) 这是为初学者准备的。不过……如果地址是数据,那为什么CPU既有地址总线又有数据总线呢? 它是否从地址总线读取指针变量的内容? ;-)(例如,PCIe 使用数据包/命令格式,没有经典的地址/数据总线机制)。也有这样的 RAM 设计(还记得 RAMBUS 吗?)无论如何,我对答案很好,对初学者来说应该足够了。如果他们有兴趣深入了解,请离开 cmets。 注意最后一个例子:不需要取消引用指针来表现未定义的行为。与其他任何事情一样,不确定的内容(p2 的值的情况)在其本质上会在 评估 时引发未定义的行为,更不用说通过 取消引用 进一步疯狂。是的,这是一个不同的问题,但密切相关。总之,声明“在初始化之前使用指针总是一个错误”。是真的,但“使用”不仅限于解除引用。【参考方案4】:

发生这种情况是因为 没有为 指针 char* ptr 分配内存。 在这种情况下,您必须为指针动态分配内存。

malloc()calloc() 两个函数可用于dynamic memory allocation

试试这个代码:-

char* ptr;
ptr = malloc(50); // allocate space for 50 characters.
strcpy(ptr, "hello world");

当使用*ptr 结束时,不要忘记deallocate memory*ptr 分配的内存。这可以使用free() 函数来完成。

free(ptr);  // deallocating memory.

动态分配内存的大小可以通过realloc()来改变。

char *tmp = realloc(ptr, 100); // allocate space for 100 characters.
if (! tmp) 
    // reallocation failed, ptr not freed
    perror("Resize failed");
    exit(1);       

else 
    // reallocation succeeded, old ptr freed
    ptr = tmp;

在大多数情况下,“分段错误”是由于内存分配错误或数组超出范围的情况而发生的。

【讨论】:

【参考方案5】:

    指针只指向一个内存位置。您创建了一个指针,但尚未绑定到内存位置。 strcpy 想让你传递两个指针(第一个不能是常量),它们指向两个字符数组,像这样的签名:

    char * strcpy ( char * destination, const char * source );
    

    示例用法:

    char* ptr = malloc(32);  
    strcpy(ptr, "hello world");
    
    char str[32];  
    strcpy(str, "hello world");
    

    您可以尝试以下代码 sn-p 读取字符串直到到达换行符(*您还可以添加其他空白字符,例如 "%[^\t\n]s"(tab, newline) 或 "%[^ \t\n]s" (空格、制表符、换行符))。

    char *ptr = malloc(32);
    scanf("%31[^\n]", ptr);
    

    (在现实生活中,不要忘记检查来自scanf()的返回值!)

【讨论】:

在现实生活中,别忘了查看malloc()的返回值!

以上是关于将数据复制/扫描/读取到未初始化的指针时发生崩溃或“分段错误”的主要内容,如果未能解决你的问题,请参考以下文章

当我们在 C 中取消引用 NULL 指针时,操作系统会发生啥?

IsBadReadPtr 的最有效替代品?

Spark-xml 在读取处理指令时崩溃

SqlServer 复制中将大事务分成小事务分发

用指针影响字符串[关闭]

来自子项的scanf扫描父母已经扫描的内容