为什么调用 malloc() 没有区别?

Why does calling malloc() not make a difference?

这是一个基本示例:

#include <all the basic stuff>

int main(void) {
    char *name = (char *) malloc(2 * sizeof(char));
    if(name == NULL) {
        fprintf(stderr, "Error: Unable to allocate enough memory!\n");
        return EXIT_FAILURE;
    }
    strcpy(name, "Bob Smith");
    printf("Name: %s\n", name);
    free(name);
    return EXIT_SUCCESS;
}

因为我只分配了2个字节的信息(2个字符),所以在执行strcpy时应该会出现某种错误,对吧?这不会发生,相反它只是复制字符串,打印出来,释放内存并成功退出。为什么会出现这种情况,如何正确使用malloc?

您的程序调用 undefined behavior.

未定义的行为是超出语言规范的行为。根据定义,这意味着您不能保证获得任何类型的明确定义的行为(例如错误)。该程序明确无效。

当您使用 strcpy 时,该函数只是假设您传递给它的缓冲区足够大,可以容纳您要复制的字符串。如果假设错误,它会尝试写入缓冲区外的区域。如果发生这种情况,程序就属于 C 规范的这种情况,在 J.2 Undefined behavior:

The behavior is undefined in the following circumstances:

  • Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object

因此,要正确使用 strcpy,您必须手动确保上述关于字符串长度和缓冲区长度的假设成立。为此,一个简单的方法是将缓冲区的长度保存在某处,计算要复制的字符串的长度,然后比较它们。

例如:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    size_t bufferSize = 2 * sizeof(char);       
    char *name = malloc(bufferSize);
    if(name == NULL) {
        fprintf(stderr, "Error: Unable to allocate enough memory!\n");
        return EXIT_FAILURE;
    }
    size_t length = strlen("Bob Smith");
    if(length + 1 > bufferSize) {
        fprintf(stderr, "Error: The target buffer is too small!\n");
        return EXIT_FAILURE;
    }
    strcpy(name, "Bob Smith");
    printf("Name: %s\n", name);
    free(name);
    return EXIT_SUCCESS;
}

作为不相关的旁注,您会注意到我 didn't cast the result of malloc,因为 void* 可以隐式转换为 char*


最后一点:

当您试图确保代码的正确性时(无论是因为您正在学习该语言还是因为您打算发布该软件),C 的这一方面可能听起来不切实际。

这就是为什么有些工具会在您的程序执行无效操作时向您报错。 Valgrind 就是这样一种工具(正如 Jonathan Leffler 在评论中提到的那样)。

  1. malloc 将 return null 如果分配内存失败,例如你的系统内存不足。 2 个字节不太可能!
  2. 如果复制的字节数多于分配的字节数,则会出现未定义的行为。那个未定义的行为可能是您的程序按预期运行!
  3. 作为您询问的关于 "correct" 使用 malloc 的更一般说明,我建议使用 char *name = malloc(2 * sizeof(*name));。更简洁,忘记包含stdlib.h也不会隐藏错误,以后改name的类型也方便
  4. 关于 strcpy 的安全使用,您不应将其替换为 strncpy,因为如果缓冲区不够大(不是空终止)并且可能效率低下,它本身就不安全。检查您的系统是否有 strcpy_sstrlcpy.

尝试:

strncpy(name, "Bob Smith", 2 * sizeof(char));

如果你用AddressSanitizer编译并运行它,你会得到一个错误报告:

$ gcc -g a.c -Wall -Wextra -fsanitize=address
$ ./a.out
=================================================================
==3362==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000eff2 at pc 0x7f9ff2b02dc4 bp 0x7fffe9190650 sp 0x7fffe918fdf8
WRITE of size 10 at 0x60200000eff2 thread T0
    #0 0x7f9ff2b02dc3 in __asan_memcpy (/lib64/libasan.so.2+0x8cdc3)
    #1 0x4009df in main /home/m/a.c:11
    #2 0x7f9ff26d678f in __libc_start_main (/lib64/libc.so.6+0x2078f)
    #3 0x400898 in _start (/home/m/a.out+0x400898)

0x60200000eff2 is located 0 bytes to the right of 2-byte region [0x60200000eff0,0x60200000eff2)
allocated by thread T0 here:
    #0 0x7f9ff2b0ea0a in malloc (/lib64/libasan.so.2+0x98a0a)
    #1 0x400977 in main /home/m/aa.c:6
    #2 0x7f9ff26d678f in __libc_start_main (/lib64/libc.so.6+0x2078f)

SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 __asan_memcpy

答案已经足够多了,我会尝试将它们提升到更基本的水平,并为您提供以下根本原因:

C 不包括任何边界检查。

好处是,C 运行时非常小且高效。回溯是,对于您在问题中所做的事情,您通常不会收到任何错误消息……只是(可能在错误本身很久之后)错误的行为甚至崩溃。

好吧 strcpy(name, "Bob Smith"); 将调用 未定义的行为name 不足以存储 "Bob Smith"。 解决方案是 -

 char a[]="Bob Smith";
 char *name = malloc(strlen(a)+1);   //you should not cast return of malloc
 if(name == NULL) { 
    fprintf(stderr, "Error: Unable to allocate enough memory!\n");
    return EXIT_FAILURE;
 }
strncpy(name,a,strlen(a));

为什么向缓冲区写入的数据 malloc() 可以超过缓冲区的大小?除了无法预测未定义行为的结果这一事实之外,实际上还有一个解释说明为什么有时向 malloc() 缓冲区写入比字节数更多的字节似乎是完全安全的你要的。

这是因为 C standard7.20.3 内存管理函数 节中设置的要求的含义:

The order and contiguity of storage allocated by successive calls to the calloc, malloc,and realloc functions is unspecified. The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated).

注意斜体文字:"The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object"。

那些对齐限制意味着 malloc() 和相关函数实际上必须以对齐的块分配内存,并且任何对 malloc() 的成功调用很可能实际上 return 内存这是 malloc() 正在运行的对齐限制的精确倍数。

在 IIRC 具有 8 字节对齐限制的 x86 机器上,诸如 malloc( 11 ) 的调用可能 return 指向实际上是 16 字节的缓冲区的指针。

这就是为什么覆盖 malloc() 缓冲区的末尾有时似乎无害的原因之一。