当使用sprintf复制指向字符串的指针时,我有一些堆栈转储的代码。我试图将动物的内容复制到一个名为output的新指针数组中。但是,我得到了一个堆栈转储。
输出中应该包含以下内容: 新的动物兔 新的动物马 新动物驴
我是以正确的方式解决这个问题吗?
非常感谢,
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void p_init(const char **animals, char **output);
int main(int argc, char **argv)
{
char *animals[] = {"rabbit", "horse", "donkey", '\0'};
char **prt_animals = animals;
char *output[sizeof(*animals)];
/* print the contents here */
while(*prt_animals)
{
printf("Animal: %s\n", *prt_animals++);
}
/* copy and update in the output buffer */
p_init(*&animals, *&output);
getchar();
return 0;
void p_init(const char **animals, char **output)
{
while(*animals)
{
sprintf(*output, "new animal %s", *animals);
*output++;
}
}
答案 0 :(得分:6)
数组animals
是一个指针数组。它不是某种大小的缓冲区数组。因此,如果你这样做
sizeof(*animals)
您将获得该数组的第一个元素的sizeof。相当于
sizeof(char*)
因为你的数组存储了指针。所以,在读取行
char *output[sizeof(*animals)];
在一个数组中分配4或8个指针(取决于平台上指针的宽度。通常是4或8)。但那当然没有意义!你想要做的是创建一个与animals
大小相同的指针数组。您必须首先获得动物数组的总大小,然后除以一个元素的大小
char *output[sizeof(animals)/sizeof(*animals)];
现在,这就是你想要的。但是指针仍然具有不确定的值...接下来,您使用*&animals
传递数组(对于另一个传递相同)。为什么?您可以直接传递animals
。取其地址然后取消引用与首先无所事事相同。
然后在你调用的函数中,将animal
中元素指向的字符串复制到某个不确定的目标(记住output
数组的元素 - 指针 - 还有不确定的值。尚未分配它们!)。首先必须分配适量的内存并使元素指向它。
while(*animals) {
// now after this line, the pointer points to something sensible
*output = malloc(sizeof("new animal ") + strlen(*animals));
sprintf(*output, "new animal %s", *animals);
output++; // no need to dereference the result
animals++; // don't forget to increment animals too!
}
另外,关于上面的尺寸
你必须确定一件重要的事情。这是我们计算尺寸的方式。无论你做什么,一定要确保你的绳子有足够的空间! C字符串由字符和终止空字符组成,它标记字符串的结尾。因此,*output
应指向至少一样大的缓冲区,以便它包含"new animal "
和*animals
的空间。第一个包含11个字符。第二个取决于我们实际复制的内容 - 它的长度是strlen
返回的长度。所以,总的来说我们需要
12 + strlen(*animals)
所有字符的空间,包括终止null。现在,将该数字硬编码到代码中并不是一种好方法。前缀可能会更改,您可能忘记更新一个或两个字符的数字或错误计数。这就是我们使用sizeof
的原因,我们提供了我们希望预先添加的字符串文字。回想一下sizeof
表达式计算其操作数的大小。您可以在main
中使用它来获取数组的总大小。现在您将它用于字符串文字。所有字符串文字都是字符数组。字符串文字由您键入以及到空字符的字符组成。因此,以下条件成立,因为strlen
计算C字符串的长度,并且不包括终止空字符到其长度
// "abc" would have the type char[4] (array of 4 characters)
sizeof "..." == strlen("...") + 1
我们不必除以一个元素的大小,因为char的sizeof是一个,所以它不会有所作为。为什么我们使用sizeof
而不是strlen?因为它已经考虑了终止空字符,并且它在编译时进行评估。编译器可以直接替换sizeof表达式返回的大小。
答案 1 :(得分:2)
char *output[sizeof(*animals)];
创建一个指向char
的指针大小为4的数组。但是,它不会为这些指针分配内存。这些数组成员包含垃圾(即指向您不拥有的内存)。尝试写入该内存会调用UB。在您的情况下,UB通过堆栈转储表现出来。您的函数p_init
的其他一些问题如下所示:
void p_init(const char **animals, char **output)
{
/* runs an infinite loop -- since *animals is never incremented */
/* need to pass size of *animals so that you can terminate your loop */
while(*animals)
{
/* allocate some memory */
sprintf(*output, "new animal %s", *animals);
*output++;
}
}
固定代码将是这样的:
void p_init(const char ** animals, const size_t nanimals, char **output)
{
const char **w = animals;
size_t len = 0;
while (w < animals + nanimals)
{
len = strlen(*w);
*output = malloc(len + sizeof "new animal " + 1);
sprintf(*output, "new animal %s", *w);
output++;
w++;
}
}
int main(int argc, char **argv)
{
char *a[] = { "rat", "dog", "lion" };
char *o[ sizeof a/ sizeof *a ];
p_init((const char**)a, sizeof a / sizeof *a, o);
for (size_t i = 0; i < sizeof a / sizeof *a; ++i) printf("%s\n", o[ i ]);
for (size_t i = 0; i < sizeof a / sizeof *a; ++i) free(o[ i ]);
return 0;
}
随意扔掉所需的标题。
答案 2 :(得分:2)
您没有在输出数组中分配任何空间来放入副本。在使用sprintf复制到该缓冲区之前,您需要使用malloc来分配一些空间。
void p_init(const char **animals, char **output)
{
while(*animals)
{
size_t stringSize = 42; /* Use strlen etc to calculate the size you need, and don't for get space for the NULL! */
*output = (char *)malloc(stringSize);
sprintf(*output, "new animal %s", *animals);
output++;
animals++;
}
}
完成后,不要忘记在已分配的内存上调用free()。
答案 3 :(得分:1)
首先,
是什么意思p_init(*&animals, *&output);
而不是
p_init(animals, *&output);
其次,由于here解释的原因,将char **转换为const char **是违法的。
最后,你的主要问题是测试
while (*animals)
当你到达动物阵列末尾的空字符串时,你期望失败的是错误的。该语句实际上是检查指向该字符串的指针是否为NULL,而不检查该字符串是否为EMPTY。空字符串(包含单个字符'\ 0'的字符串)与空指针不同。
换句话说,一旦你到达了animals数组的最后一个元素,* animals就会计算出一个非NULL指针,它恰好指向一个空的字符串。因此,测试通过了,你的循环将永远持续下去(好吧,它一直持续到你在动物阵列末端运行足够远,导致段错误)。
您可以通过在创建animals数组时将'\ 0'替换为NULL来修复此问题,或者您可以更改while检查以检查strlen(* animals)== 0(或任何其他检查空的方法)字符串而不是空指针。
编辑:请注意,其他人指出了我错过的同样严重的问题。
答案 4 :(得分:1)
你没有创建一个非常大的缓冲区 - sizeof(*animals)
是sizeof(char*)
,在32位系统上是4个字节;你不是在任何地方创建输出字符串;并且您没有使用诸如snprintf
之类的安全机制来写入缓冲区,因此您会崩溃而不是安全失败。
对这些进行修复,保持零终止数组使用:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void p_init ( const char **animals, char **output );
int main(int argc, char **argv) {
// no reason to use char 0 rather than int 0 to mark end here
const char* animals[] = {"rabbit", "horse", "donkey", "pig", 0};
printf("sizeof(*animals) = %zd\n", sizeof(*animals));
printf("number of elements in animals = %zd\n", sizeof(animals) / sizeof(*animals));
char *output[sizeof(animals)/sizeof(*animals)];
// print animals
for ( const char**p = animals; *p; ++p)
printf ( "Animal: %s\n", *p );
// format animals to output
p_init ( animals, output);
// print output
for ( char**p = output; *p; ++p)
printf ( "Animal: %s\n", *p );
// free output
for ( char**p = output; *p; ++p )
free(*p);
return 0;
}
void p_init ( const char **animals, char **output ) {
while ( *animals ) {
size_t len = strlen ( *animals );
char* buf = malloc ( len + 13 );
snprintf ( buf, len + 13, "new animal %s", *animals );
*output = buf;
++animals;
++output;
}
*output = 0;
}