如果我尝试访问超出malloc()分配区域的内存会发生什么?

6

我使用了char* memoryChunk = malloc ( 80* sizeof(char) + 1);来分配一块内存。如果我尝试在81个单位之外的内存位置写入数据,会遇到什么问题?我应该怎么做才能避免这种情况发生?

void testStage2(void) {
 char c_str1[20] = "hello";
 char* ut_str1;
 char* ut_str2;

 printf("Starting stage 2 tests\n");
 strcat(c_str1, " world");
 printf("%s\n", c_str1); // nothing exciting, prints "hello world"

 ut_str1 = utstrdup("hello ");
 ut_str1 = utstrrealloc(ut_str1, 20);
 utstrcat(ut_str1, c_str1);
 printf("%s\n", ut_str1); // slightly more exciting, prints "hello hello world"

 utstrcat(ut_str1, " world");
 printf("%s\n", ut_str1); // exciting, should print "hello hello world wo", 'cause there's not enough room for the second world
}

char* utstrcat(char* s, char* suffix){
 int i = strlen(s),j;
 int capacity = *(s - sizeof(unsigned) - sizeof(int));
 for ( j =0; suffix[j] != '\0'; j++){
  if ((i+j-1) == 20)
   return s;
  s[i+j] = suffix[j];
 }
 //strcpy(s, suffix);
 s[i + j] = '\0';
 return s;
}// append the suffix to s

3
根据定义,sizeof(char) 的值为 1。如果你想要乘以一个大小,请使用 sizeof *memoryChunk,例如:malloc((80 + 1) * sizeof *memoryChunk);。这样做的好处是,如果将来将 memoryChunk 改为 wchar_t*,则不需要修改 malloc 参数。 - pmg
第一个 strcat() 很危险。strcat() 要求目标缓冲区有足够的空间来追加新字符串;你的代码没有任何保证。 - Carl Norum
1
由于c_str被分配了20个位置,足以容纳“hello world”。 - user133466
好的呼叫 - 完全没读懂那个。我看到了 c_str1[] = "hello"; - Carl Norum
从问题中删除(C编程)-这就是标签的用途。 - Jed Smith
5个回答

16
什么阻止我写入超过81个单位的内存位置?
没有。但是这样做会导致未定义的行为。这意味着任何事情都可能发生,你不应该依赖它两次做同样的事情。99.999%的情况下这是一个错误。
我该怎么做才能防止这种情况发生?
在访问(读取或写入)指针之前,始终检查它们是否在范围内。将字符串传递给字符串函数时,请始终确保以\0结尾。
您可以使用调试工具(如valgrind)来帮助您定位与越界指针和数组访问相关的错误。
stdlib的方法
对于您的代码,您可以有类似utstrcat但带有最大大小(即缓冲区大小)的utstrncat
stdc++的方法
您还可以创建数组结构/类或在C++中使用std::string。例如:
typedef struct UtString {
    size_t buffer_size;
    char *buffer;
} UtString;

然后让您的函数在该位置上操作。您甚至可以使用此技术进行动态重新分配(但这似乎不是您想要的)。
结束缓冲标记方法
另一种方法是拥有“缓冲区结尾”标记,类似于“字符串结尾”标记。当遇到该标记时,不要写入该位置或其前面的位置(对于字符串结尾标记),或者您可以重新分配缓冲区以获得更多空间。
例如,如果您将`"hello world\0xxxxxx\1"`作为字符串(其中`\0`是字符串结尾标记,`\1`是缓冲区结尾标记,而`x`是随机数据),则附加`" this is fun"`将如下所示:
hello world\0xxxxxx\1
hello world \0xxxxx\1
hello world t\0xxxx\1
hello world th\0xxx\1
hello world thi\0xx\1
hello world this\0x\1
hello world this \0\1
*STOP WRITING* (next bytes are end of string then end of buffer)

你的问题

你的代码问题在这里:

  if ((i+j-1) == 20)
   return s;

尽管您在溢出缓冲区之前停下来了,但是您没有标记字符串的结束。
您可以使用 break 来提前结束 for 循环,而不是返回。这将导致在 for 循环后运行的代码。这会设置字符串的结束标记并返回字符串,这正是您想要的。
此外,我担心您分配的有一个 bug。您在分配大小之前增加了 +1,是吗?有一个问题:unsigned 通常不是 1 个字符;您需要 + sizeof(unsigned)。我还会编写 utget_buffer_sizeutset_buffer_size,以便更轻松地进行更改。

2
你低估了这可能是一个错误的概率;小数点后面应该至少再有几个9。 - Jonathan Leffler
strager,感谢你的尝试。我已经发布了我的strcat版本。*(s - sizeof(unsigned) - sizeof(int))是因为那里存储了最大容量。我认为我的方法与你上面描述的方法有些相似。但是在第二个“wor”后,我仍然会得到一些乱码字符。 - user133466
此外,“utstrcat()”是从哪里来的?它似乎并不是很标准...话虽如此,问题仍在使用它-但我仍然想知道它来自哪里。 - Jonathan Leffler
@Jon 刚刚在底部发布了 utstrcat。 - user133466
@Leffler,metashockwave正在重新创建一些标准函数作为练习。@metashockwave,我已经更新了我的答案,包括解决你特定问题的方案。(你在问题中没有表述得很清楚,请下次确切地说明问题!) - strager

3

没有任何东西会阻止你这样做。如果你这样做,任何事情都可能发生:程序可能会像什么都没发生一样继续执行,它可能会现在崩溃,也可能以后崩溃,甚至可能抹掉你的硬盘。这是 未定义行为 的领域。

有许多工具尝试检测或减轻这些问题,但没有什么是百分之百可靠的。其中一个工具是valgrind。valgrind观察你程序的内存访问模式,并通知你这样的问题。它通过在某种虚拟机中运行你的程序来实现这一点,因此它会显著降低你程序的性能,但如果正确使用,它可以帮助你捕捉大量错误。


0

没有任何东西能阻止你继续编写超过此范围的内容,而后果取决于此范围之外的内容。标准黑客技巧(缓冲区溢出)用于攻击不检查并确保不会覆盖缓冲区限制的程序。

正如其他帖子中提到的那样,你只需要仔细编程。不要使用像strlen、strcpy这样的调用 - 而是使用长度受限的版本,例如strncpy等。


strncpy有时不会终止它的缓冲区,因此它会将一个问题换成另一个问题;请谨慎使用。snprintf是另一个选择。 - M.M

0

-4
发生的事情:没有任何反应,或者您的程序会抛出 SIGSEGV。 您应该做什么:仔细编写程序。使用诸如valgrind之类的工具。

6
“nothing”和“SIGSEGV”不幸地并非仅有的两种可能性。发生了什么事情无法预测。 - Pascal Cuoq

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接