下面是一个基本的例子:
#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: %sn", name);
free(name);
return EXIT_SUCCESS;
}
因为我只分配了2个字节的信息(2个字符),所以当我执行strcpy时应该会出现某种错误,对吗?这不会发生,相反,它只是复制字符串,打印出来,释放内存并成功退出。为什么会发生这种情况,如何正确使用malloc ?
您的程序调用了未定义行为。
未定义的行为是指超出语言规范的行为。根据定义,这意味着您不能保证获得任何类型的定义良好的行为(例如错误)。程序显式无效。
当您使用strcpy
时,该函数只是假设您传递给它的缓冲区足够大,可以容纳您想要复制的字符串。如果假设是错误的,它将尝试从缓冲区中写入一个区域。如果发生这种情况,程序就属于C规范的这种情况,在J.2中未定义行为:
因此,要正确使用在以下情况下未定义该行为:
- 对一个数组对象和一个整数类型进行指针的加减操作,产生的结果既不指向同一个数组对象,也不指向同一个数组对象。
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: %sn", name);
free(name);
return EXIT_SUCCESS;
}
作为一个无关的边注,您将注意到我没有强制转换malloc
的结果,因为void*
可以隐式地转换为char*
。
最后说明:
当您试图确保代码的正确性时(因为您正在学习这门语言或因为您打算发布该软件),C的这一方面可能听起来不切实际。
这就是为什么当你的程序做了一些无效的事情时,有一些工具会给你一个错误。Valgrind就是这样一个工具(正如Jonathan Leffler在评论中提到的)。
如果你用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
-
malloc
将返回null
,如果它分配内存失败,例如您的系统内存不足。这对于2字节来说是不可能的! - 如果你复制的字节比你分配的多,你会得到未定义的行为。这种未定义的行为可能是您的程序按照预期的行为!
- 作为您所询问的
malloc
的"正确"使用的更一般的说明,我会推荐char *name = malloc(2 * sizeof(*name));
。它更简洁,它不会隐藏错误,如果你忘记包括stdlib.h
,它会更容易改变name
的类型在未来。 - 关于
strcpy
的安全使用,你不应该用strncpy
代替它,因为如果缓冲区不够大(不是null终止)并且可能效率低下,它本身就是不安全的。检查您的系统是否有strcpy_s
或strlcpy
。
有足够多的答案,我将试着把它们变成一个更基本的层次,并给出以下作为根本原因:
C不包含任何边界检查。
这样做的好处是,C运行时非常小且高效。缺点是,你通常不会得到任何错误信息,就像你在问题中所做的那样……只是(可能在错误本身很久之后)错误的行为甚至崩溃。
为什么你可以写更多的数据到缓冲区你malloc()
比缓冲区的大小?除了你不能预测未定义行为的结果这一事实之外,实际上有一个解释,为什么有时向malloc()
'd缓冲区写入比你要求的字节数更多的字节似乎是完全安全的。
这是因为C标准7.20.3节内存管理函数中的要求:
的连续调用所分配的存储的顺序和连续度calloc
、malloc
和realloc
功能未指定。如果分配成功返回的指针被适当对齐它可以赋值给指向任何类型对象的指针,然后用于访问空间中这样一个对象或这样一个对象的数组已分配(直到空间被显式释放)。
注意斜体文本:"如果分配成功返回的指针已适当对齐,因此它可以被分配给指向任何类型对象的指针"。
这些对齐限制意味着malloc()
和相关函数必须有效地将内存分配到对齐块中,并且任何成功调用malloc()
都很可能实际上返回的内存是malloc()
所操作的对齐限制的倍数。
在x86机器上,IIRC有8字节的对齐限制,像malloc( 11 )
这样的调用可能会返回一个指向实际上是16字节的缓冲区的指针。
这就是为什么覆盖malloc()
'd缓冲区的末尾有时似乎是无害的原因之一。
那么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));
try:
strncpy(name, "Bob Smith", 2 * sizeof(char));