【问题标题】:strcpy working no matter the malloc size?无论 malloc 大小如何,strcpy 都能正常工作?
【发布时间】:2017-04-18 12:08:44
【问题描述】:

我目前正在学习 C 编程,由于我是一名 python 程序员,我对 C 的内部工作原理并不完全确定。我只是偶然发现了一件非常奇怪的事情。

void test_realloc(){
  // So this is the original place allocated for my string
  char * curr_token = malloc(2*sizeof(char));

  // This is really weird because I only allocated 2x char size in bytes 
  strcpy(curr_token, "Davi");
  curr_token[4] = 'd';
  // I guess is somehow overwrote data outside the allocated memory?
  // I was hoping this would result in an exception ( I guess not? )      

  printf("Current token > %s\n", curr_token);

  // Looks like it's still printable, wtf???
  char *new_token = realloc(curr_token, 6);
  curr_token = new_token;
  printf("Current token > %s\n", curr_token);
}


int main(){
  test_realloc();
  return 0;
}

所以问题是:为什么我能够在字符串中写入比分配大小更多的字符?我知道我应该自己处理分配的内存,但这是否意味着当我在指定内存之外写入时没有迹象表明有问题?

我想要完成的事情

  1. 分配一个 4 字符 (+ null char) 字符串,我将在其中写入我的姓名的 4 个字符
  2. 重新分配内存以容纳我名字的最后一个字符

【问题讨论】:

  • 这是未定义的行为
  • 请详细说明?
  • Undefined Behavior 一切皆有可能发生.....
  • 这是informative list of undefined behaviour。它包含一些 UB,而不是全部,并且它包含的一些可能是不正确的(只是提供信息,而不是规范)。例如“指示字符串或宽字符串实用函数访问超出对象末尾的数组(7.24.1、7.29.4)。”
  • 2 * 1 = 2 .... sizeof(char)

标签: c string pointers malloc


【解决方案1】:

知道我应该自己处理分配的内存,但这是否意味着当我在指定内存之外写入时没有迹象表明有问题?

欢迎来到 C 编程 :)。一般来说,这是正确的:您可能会做错事而不会立即收到这种情况的反馈。在某些情况下,确实,您可能会做错事而永远在运行时发现问题。但是,在其他情况下,您会看到崩溃或其他对您没有意义的行为。

关键词是未定义的行为。如果你继续用 C 语言编程,你应该熟悉这个概念。它的意思就像听起来一样:如果你的程序违反某些规则,则行为是 undefined - 它可能会做你想做的事,它可能会崩溃,它可能会做一些不同的事情。更糟糕的是,它可能大部分做你想做的事,但只是偶尔做一些不同的事情。

正是这种机制让 C 程序变得更快——因为它们在运行时不会做很多你可能习惯于从 Python 中进行的检查——但它也使 C 变得危险。很容易编写不正确的代码而没有意识到;然后稍后在其他地方进行细微的更改,或者使用不同的编译器或操作系统,代码将不再按您想要的方式运行。在某些情况下,这可能会导致安全漏洞,因为不需要的行为可能会被利用。

【讨论】:

  • 处理此类问题的推荐方法是什么?
  • @DavidČerný 使用 消毒剂 或可用的工具。 Gcc 和 Clang 编译器具有 sanitizer 选项,可以在运行时捕获您的错误(尽管它们无法捕获所有错误)。 valgrind 等工具也可以做到这一点。 静态分析工具(例如来自 Clang 的 scan-build)可以检查您的代码并发现一些错误。
  • @DavidČerný 处理此类问题的推荐方法是什么? 请小心,不要一开始就制造问题。不能保证消毒剂或工具能全部捕获。正如 davmac 所说,“欢迎使用 C 编程。”
【解决方案2】:

假设你有一个如下所示的数组。

int arr[5] = {6,7,8,9,10};

从数组的基础看,数组名是一个指向数组基元素的指针。这里,arr 是数组的名称,它是一个指针,指向基元素,即 6。因此,*arr,字面意思是,*(arr+0) 给你 6 作为输出,*(arr+1) 给你7等等。 这里,数组的大小是 5 个整数元素。现在,尝试访问第 10 个元素,尽管数组的大小是 5 个整数。 arr[10]。这不会给你一个错误,而是给你一些垃圾值。由于 arr 只是一个指针,因此取消引用以 arr+0arr+1arr+2 等形式完成。同样,您也可以使用基数组指针访问arr+10。 现在,试着用这个例子来理解你的上下文。虽然您只为字符分配了 2 个字节的内存,但您可以使用指针访问超出分配的两个字节的内存。因此,它不会给您带来错误。另一方面,您可以预测机器上的输出。但是不能保证您可以预测另一台机器上的输出(可能是您在机器上分配的内存被零填充,并且可能是那些特定的内存位置第一次被使用!)。在声明中, char *new_token = realloc(curr_token, 6); 请注意,您正在为 curr_token 指向 new_tokenpointer 的指针所指向的 6 字节数据重新分配内存。现在,new_token 的初始大小将是 6 个字节。

【讨论】:

  • 谢谢,我只是很困惑,因为在 python 中这是完全不同的(显然)
  • “这不会给你一个错误,而是给你一些垃圾值” - 给出一个错误完全符合规范。 大多数 编译器不会这样做。然而,在调用技术上未定义的行为时开始假设任何特定行为是危险的。
  • 有时不熟悉概念并不少见。我们所需要的只是打磨和练习。尝试执行相同的操作,然后得出结论。我的朋友,理论解释和实际实现有很大的不同!
  • 问题是“实际实现”会随着时间而改变。编译器越来越多地利用未定义行为的概念作为允许优化的机制。多年来,您可以通过检查结果是否小于 0 来可靠地检查有符号整数溢出;这不再是真的。像for (int i = 0; i >= 0; i++) 这样的循环现在经常被编译器变成无限循环。依赖能够“结束”访问数组“元素”可能同样会导致问题,如果不是现在,将来也会出现问题。
  • 就我使用 gcc 4.2.3 到 6.3.0 而言,同样的概念也适用。我无法清除未来并使用我们需要的所有 AI 和永远编码来开发我的代码!我的回答只是针对这个问题,而不是解决黑洞相对论中的某个方程!
【解决方案3】:

通常malloc 的实现方式是分配与段落对齐的内存块(基本对齐),等于 16 个字节。

因此,当您请求分配例如 2 个字节时,malloc 实际上分配了 16 个字节。这允许在调用realloc 时使用相同的内存块。

根据 C 标准(7.22.3 内存管理函数)

  1. ...分配成功时返回的指针已适当对齐,因此 可以将它分配给指向任何类型对象的指针 具有基本对齐要求,然后用于访问这样的 分配空间中的对象或此类对象的数组 (直到空间被显式释放)。

但是,您不应依赖此类行为,因为它不规范,因此被视为未定义的行为。

【讨论】:

  • 所以 malloc 分配的内存块大小与我指定的不同?
  • @DavidČerný 你。 malloc 必须保证将分配的任何对象都正确对齐。
【解决方案4】:

在 C 中不执行自动边界检查。 程序行为是不可预测的。 如果你在为另一个进程保留的内存中写入,你会以一个 Segmentation fault 结束,否则你只会损坏数据,ecc...

【讨论】:

  • 哇,这太野蛮了,如果我知道了这个,至少会让我成为一个更好的程序员。
  • 在大型程序中实现无内存泄漏并非易事。始终检查您没有超出数组限制,您不再使用的内存被释放,您没有取消引用空指针和其他类似的事情,这一点非常重要。
  • C 不要求自动边界检查。一个实现可能有边界检查。
  • re 在 C: 中不执行自动边界检查:... Google bounds checking in C。那里有实现。
猜你喜欢
  • 2013-03-30
  • 2015-09-17
  • 2017-12-15
  • 1970-01-01
  • 1970-01-01
  • 2020-09-05
  • 1970-01-01
  • 2017-08-24
  • 2014-04-06
相关资源
最近更新 更多