我只需运行以下代码即可获得Bus error: 10
(运行时):
char *x;
char *y = "the quick";
sprintf(x, "%s brown fox jumps over the lazy dog", y);
环顾四周,我看到了一些帖子,问题似乎是我试图修改一个字符串文字,但在上面的代码中,我认为我不是。此外,如果我尝试使用malloc
为x
分配一些内存,那么一切都可以,如果我分配的内存少于存储整个字符串所需的内存,那么即使我分配了0
,它也可以。
char *x = malloc(0);
char *y = "the quick";
sprintf(x, "%s brown fox jumps over the lazy dog", y);
printf("%sn", x); // the quick brown fox jumps over the lazy dog
我也知道这在某种程度上与内存分配有关,但我想指定x
,而不告诉编译器它需要多少内存,因为y
可能是一个非常长的字符串,甚至是一个数字。。。
我应该如何做我正在尝试做的事情,只使用字符指针(而不是字符数组),而不必知道x
的最终长度,例如,不必计算x
的最大大小,然后分配内存?
问题是x
在尝试使用0
进行分配后没有指向有效的内存块。要成功地将这两个字符串连接到新分配的块中,您需要知道保存这两个字符所需的字符总数。获得总数的一种方便方法是使用:
snprintf (NULL, 0, "format-string", vars, ...);
其将返回组合字符串所需的字符总数。然后分配total + 1
为nul终止字符提供空间。你可以用
#include <stdio.h>
#include <stdlib.h>
int main (void) {
char *x;
char *y = "the quick";
/* use snprintf (NULL, 0, "format", vars...) to get no. of chars needed */
size_t needed = snprintf (NULL, 0, "%s brown fox jumps over the lazy dog", y);
x = malloc (needed + 1); /* allocate for x, +1 for the nul-terminating char */
if (!x) { /* validate EVERY allocation */
perror ("malloc-x");
return 1;
}
/* now use sprintf to joins string in newly allocated block */
sprintf (x, "%s brown fox jumps over the lazy dog", y);
puts (x); /* output result */
free (x); /* don't forget to free what you allocate */
}
示例使用/输出
$ ./bin/snprintfNULL0
the quick brown fox jumps over the lazy dog
内存使用/错误检查*
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有2个责任:(1)始终为内存块保留一个指向起始地址的指针,因此,(2)当不再需要时,它可以被释放。
您必须使用内存错误检查程序来确保您不会试图访问内存或在分配的块的边界之外写入,尝试读取或基于未初始化的值进行条件跳转,最后确认您释放了所有分配的内存。
对于Linux,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序即可
$ valgrind ./bin/snprintfNULL0
==31830== Memcheck, a memory error detector
==31830== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==31830== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==31830== Command: ./bin/snprintfNULL0
==31830==
the quick brown fox jumps over the lazy dog
==31830==
==31830== HEAP SUMMARY:
==31830== in use at exit: 0 bytes in 0 blocks
==31830== total heap usage: 2 allocs, 2 frees, 1,069 bytes allocated
==31830==
==31830== All heap blocks were freed -- no leaks are possible
==31830==
==31830== For counts of detected and suppressed errors, rerun with: -v
==31830== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
请始终确认您已经释放了分配的所有内存,并且没有内存错误。
看看这个,如果你有问题,请告诉我。
。。。问题似乎是我试图修改一个字符串文字,但在上面的代码中,我认为我不是。
正确,您没有修改ant字符串文字。y
指向字符串文字,但没有代码通过y
对字符串进行修改。
问题(在第一个代码片段中)是x
未初始化。
CCD_ 16解决了这个问题,但出现了一个新的问题。当你写的时候,你分配的内存太少了。
。。。问题似乎是我试图修改一个字符串文字,但在上面的代码中,我认为我不是。
那怎么可能呢?
发生的情况是向数组外的内存写入(越界访问)。C标准将其定义为";未定义行为";。
"未定义行为";意味着任何事情都可能发生。例如,程序被允许崩溃。但这个程序也可能(看起来)像你预期的那样工作。这件事发生在你身上。。。你的代码是错误的-有未定义的行为-但幸运的是(或者更确切地说是不幸运的),它似乎只是起作用了。
sprintf
存储字符串,其中包括指定到第一个参数指向的数组中的转换。程序员有责任传递一个足够大的数组来容纳转换后的字符串,包括它的最终终止符。
在第一个示例中,通过未初始化的指针进行写入具有未定义的行为,正如您所观察到的,这可能会导致崩溃。
在第二个示例中,malloc(0)
可以返回NULL
或不应用于写入的有效指针,因为它指向大小为0
的数组。在这两种情况下,sprintf
都会调用未定义的行为,在这种情况下,测试时您的机器会产生预期的行为,因为无效写入未被检测到,但可能会造成只有稍后才能看到的副作用。
这是一个修改版本:
char x[80];
char *y = "the quick";
sprintf(x, "%s brown fox jumps over the lazy dog", y);
printf("%sn", x); // the quick brown fox jumps over the lazy dog
使用snprintf()
并传递目标数组的实际大小会安全得多,以避免在数组太短时出现未定义的行为。字符串将被截断以适应数组,但返回值将是转换生成的字节总数:
char x[25];
char *y = "the quick";
int n = snprintf(x, sizeof x, "%s brown fox jumps over the lazy dog", y);
printf("%s, needs %d bytesn", x, n + 1); // the quick brown fox jump, needs 44 bytes
您可以通过使用空指针和零大小调用snprintf
来确定所需的大小:
size_t minimum_size = 1 + snprintf(NULL, 0, "%s brown fox jumps over the lazy dog", y);
一些系统还有一个可供选择的函数asprintf
,它可以自动分配所需的内存,但这是一个GNU扩展,目前还没有广泛使用。
坏消息是,如果不计算缓冲区的最终大小,就无法执行此操作。
好消息是,您可以让snprintf为您进行计算。
const char *y = "the quick";
const char *fmt = "%s brown fox jumps over the lazy dog";
int sizeneeded = snprintf(NULL, 0, fmt, y) + 1;
char *dest = malloc(sizeneeded);
snprintf(dest, sizeneeded, fmt, y);
printf("%sn", dest); // the quick brown fox jumps over the lazy dog
免责声明:为简洁起见,此处不检查malloc
的返回值是否为NULL
。