将字符串复制到缓冲区

时间:2009-03-19 17:02:40

标签: c arrays copy pointers

当使用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++;
    }
}

5 个答案:

答案 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;
}