为啥 strcpy 会触发全局变量的分段错误?
Posted
技术标签:
【中文标题】为啥 strcpy 会触发全局变量的分段错误?【英文标题】:Why does strcpy trigger a segmentation fault with global variables?为什么 strcpy 会触发全局变量的分段错误? 【发布时间】:2010-09-12 11:31:21 【问题描述】:所以我有一些 C 代码:
#include <stdio.h>
#include <string.h>
/* putting one of the "char*"s here causes a segfault */
void main()
char* path = "/temp";
char* temp;
strcpy(temp, path);
这会编译、运行并按其外观运行。但是,如果将一个或两个字符指针声明为全局变量,则 strcpy 会导致分段错误。为什么会这样?显然我对范围的理解有误。
【问题讨论】:
因为我认为它实际上并不能解决问题,所以我只是评论说 strncpy 比 strcpy 更推荐。 Josh Gagnon:实际上,当输入字符串的长度 >= 缓冲区时,strncpy 不会放置空终止符。如果您知道缓冲区足够大, strcpy 是完全安全的。否则,使用snprintf(buffer, buffer_len, "%s", src)
,因为 snprintf 总是放置一个空终止符(只要确保 buffer_len > 0)。
@Josh:我更喜欢strlcpy
。不幸的是 glibc 不支持它,所以我没有太多机会使用它。我想我总是可以推出自己的实现并将其添加到我的空检查 malloc 和文件相关函数的个人头库中,但它仍然让我烦恼的是,许多版本的 Unix 都有它,而 Linux 通常没有。
事实上,我在大学的最后一个学期几乎把我搞砸了,因为我正在为我的一门课测试第四个也是最后一个作业等级的程序(我已经错过了一个作业,因为认为它应该在实际发生三天后到期)远程意外登录到 Unix 服务器而不是 Linux 服务器而不注意,并在那里编译和运行它就好了,但是考试时间到了,我收到一封来自其中一个的电子邮件班级的助教说“你的程序不会在 [Linux] 测试盒上运行或编译”。
(幸运的是,我已经包含了 strlcat
和 strncat
的行 [与 strlcpy
和 strncpy
在工作方式和实现方式方面的差异相似]正在执行的操作,因此只需注释掉一行并取消注释之后的行。)...没想到会占用这么多注释框。
【参考方案1】:
正如其他海报所提到的,问题的根源在于 temp 未初始化。当声明为堆栈上的自动变量时,它将包含该内存位置中发生的任何垃圾。显然,对于您正在运行的编译器+CPU+OS,该位置的垃圾是一个有效的指针。 strcpy “成功”在于它没有段错误,但实际上它将字符串复制到内存中其他位置的任意位置。这种内存损坏问题让世界各地的 C 程序员感到恐惧,因为它非常难以调试。
当您将临时变量声明移动到全局范围时,它被放置在 BSS 部分并自动归零。尝试取消引用 *temp 会导致段错误。
当您将 *path 移动到全局范围时,*temp 会在堆栈上向上移动一个位置。该位置的垃圾显然不是有效指针,因此取消引用 *temp 会导致段错误。
【讨论】:
正如预期的那样,交换变量声明的顺序会使程序出现段错误。谢谢!【参考方案2】:临时变量不指向任何存储(内存)并且未初始化。
如果 temp 被声明为 char temp[32];
,那么无论在哪里声明代码都可以工作。但是,用这样的固定大小声明 temp 还有其他问题,但这是另一天的问题。
现在,为什么它在全局而不是本地声明时会崩溃。运气...
当在本地声明时, temp 的值来自当时可能在堆栈上的任何值。幸运的是它指向了一个不会导致崩溃的地址。但是,它正在破坏其他人使用的内存。
当全局声明时,在大多数处理器上,这些变量将存储在将使用需求零页的数据段中。因此char *temp
看起来好像它被声明为char *temp=0
。
【讨论】:
【参考方案3】:您忘记分配和初始化 temp:
temp = (char *)malloc(TEMP_SIZE);
只要确保 TEMP_SIZE 足够大。你也可以在运行时计算这个,然后确保大小足够(至少应该是strlen(path))
【讨论】:
没有必要在 temp 初始化内存——strcpy 会处理这个,即使它只是设置初始的 null 项。 此外,它至少需要 strlen(path)+1 才能适合空项,否则会导致 Bad Things T(M)。 他的意思是你不需要 "temp[0] = 0" 行,因为 strcpy() 会为你添加 NULL 终止符。 strcpy 不关心目标字符串指向什么,只要它足够大以包含结果即可。 temp[0] = 0 是没有意义的,即使源字符串为空,仍将由 strcpy 设置。如果要清除整个字符串,则必须使用 memset (或其他) 抱歉,将 'strcpy' 误解为 'strcat'。我修正了我的答案【参考方案4】:如上所述,您忘记为 temp 分配空间。
我更喜欢strdup
而不是malloc+strcpy
。它做你想做的事。
【讨论】:
【参考方案5】:不 - 无论变量如何,这都不起作用 - 它看起来像它那样,因为你(不)幸运。您需要分配空间来存储字符串的内容,而不是让变量保持未初始化状态。
堆栈上未初始化的变量将指向几乎随机的内存位置。如果这些地址恰好是有效的,那么您的代码将践踏那里的所有内容,但您不会收到错误(但可能会在代码的其他地方出现与内存损坏相关的严重错误)。
全局变量总是失败,因为它们通常被设置为指向未映射内存的特定模式。尝试取消引用这些会立即给您一个段错误(这更好 - 将其留到以后会使错误很难追踪)。
【讨论】:
【参考方案6】:我想将亚当的第一个片段重写为
// Make temp a static array of 256 chars
char temp[256];
strncpy(temp, sizeof(temp), path);
temp[sizeof(temp)-1] = '\0';
这样你:
1. don't have magic numbers laced through the code, and
2. you guarantee that your string is null terminated.
如果源字符串的长度 >=256 个字符,则第二点是丢失源字符串的最后一个字符。
【讨论】:
使用 wchar_t 时,这不起作用。使用 sizeof 仅适用于字符。我知道,这是一个关于 chars 的问题,但我遇到了很多错误,人们将 sizeof 与 wchar_t 一起使用。【参考方案7】:需要注意的重要部分:目标字符串 dest 必须足够大以接收副本。 在您的情况下, temp 没有分配要复制到的内存。
从 strcpy 的手册页复制:
DESCRIPTION
The strcpy() function copies the string pointed to by src (including
the terminating '\0' character) to the array pointed to by dest. The
strings may not overlap, and the destination string dest must be large
enough to receive the copy.
【讨论】:
【参考方案8】:您正在调用未定义的行为,因为您没有初始化 temp
变量。它指向内存中的一个随机位置,因此您的程序可能可以工作,但很可能会出现段错误。你需要让你的目标字符串是一个数组,或者让它指向动态内存:
// Make temp a static array of 256 chars
char temp[256];
strncpy(temp, 256, path);
// Or, use dynamic memory
char *temp = (char *)malloc(256);
strncpy(temp, 256, path);
另外,使用strncpy()
代替strcpy()
以避免缓冲区溢出。
【讨论】:
亚当,让所有这些神奇的数字四处飘荡是个好主意吗? ;-) 仍然存在错误,因为如果源字符串的长度为 256,则字符串没有空终止。 不需要转换 malloc 返回的指针——malloc 返回一个 void 指针,因此不需要转换。此外,强制转换它可能会伤害您,因为它可能会隐藏在这行代码中 malloc 未正确定义的问题(因为 malloc 的隐式定义是它返回一个 int)。 @Daniel Papasian:强制转换在 C 中不是必需的,但在 C++ 中是必需的,我通常喜欢使我的 C 代码尽可能与 C++ 兼容。在 C 中不推荐使用隐式定义的函数而不声明它们,并且不应在新的 C 代码中使用它们。我总是使用 -Wall 进行编译,其中包括 -Wimplicit-function-declaration。以上是关于为啥 strcpy 会触发全局变量的分段错误?的主要内容,如果未能解决你的问题,请参考以下文章