为什么不能以这种方式复制终止null?

时间:2018-12-12 01:09:38

标签: c null

我想用以下代码复制字符串,但没有复制'\ 0'。

void copyString(char *to, char *from)
{
    do{
        *to++ = *from++;
    }while(*from);
}
int main(void)
{
    char to[50];
    char from[] = "text2copy";
    copyString(to, from);
    printf("%s", to);
}

这将输出为代码:

text2copyÇ■   ║kvu¡lvu

每次我重新运行代码时,text2copy之后的字符都会更改,因此 while(* from)可以正常工作,但会复制一些随机内容而不是'\ 0'。

text2copyÖ■   ║kvu¡lvu
text2copy╨■   ║kvu¡lvu
text2copy╡■   ║kvu¡lvu
//etc

为什么会这样?

2 个答案:

答案 0 :(得分:4)

问题在于,您永远不会在字符串末尾复制'\0'字符。要了解为什么要考虑这一点:

传入的字符串是一个常量字符串,大小恰好适合数据:

char from[] = "text2copy";

在内存中看起来像这样:

            ----+----+----+----+----+----+----+----+----+----+----+----
   other memory |  t |  e |  x |  t |  2 |  c |  o |  p |  y | \0 | other memory
            ----+----+----+----+----+----+----+----+----+----+----+----
                   ^
                 from

现在,让我们假设您已经完成了几次循环,并且您位于循环的顶部,并且from指向text2copy中的'y'字符:

            ----+----+----+----+----+----+----+----+----+----+----+----
   other memory |  t |  e |  x |  t |  2 |  c |  o |  p |  y | \0 | other memory
            ----+----+----+----+----+----+----+----+----+----+----+----
                                                           ^
                                                         from

计算机执行*to++ = *from++;,将'y'字符复制到to,然后递增tofrom。现在内存看起来像这样:

            ----+----+----+----+----+----+----+----+----+----+----+----
   other memory |  t |  e |  x |  t |  2 |  c |  o |  p |  y | \0 | other memory
            ----+----+----+----+----+----+----+----+----+----+----+----
                                                                ^
                                                              from

计算机执行} while(*from);并意识到*from是错误的,因为它指向字符串末尾的'\0'字符,因此循环结束且'\0'字符永远不会被复制。

现在您可能会认为这可以解决问题:

void copyString(char *to, char *from)
{
    do{
        *to++ = *from++;
    } while(*from);
    *to = *from; // copy the \0 character
}

它确实复制了'\0'字符,但是仍然存在问题。该代码甚至从根本上来说是有缺陷的,因为正如@JonathanLeffler在评论中所说,对于空字符串,您可以窥视字符串末尾的内存内容,并且因为未分配给您访问它而导致未定义的行为:< / p>

            ----+----+----
   other memory | \0 | other memory
            ----+----+----
                   ^
                 from

计算机执行*to++ = *from++;,将'\0'字符复制到to,然后同时递增tofrom,这从点到内存使您不需要自己的:

            ----+----+----
   other memory | \0 | other memory
            ----+----+----
                        ^
                      from

现在,计算机将执行}while(*from);并访问非您的内存。您可以毫无问题地将from指向任何地方,但是当from指向不属于您的内存时取消引用是不确定的行为。

我在注释中所做的示例建议将复制的值保存到一个临时变量中:

void copyString(char *to, char *from)
{
    int test;
    do{
        test = (*to++ = *from++); // save the value copied
    } while(test);
}

我建议采用特定方法的原因是为了向您表明问题出在您要测试的内容,而不是事后要测试循环条件。如果您保存复制的值,然后稍后再测试该保存的值,则在测试该字符之前先将其复制(以便复制\ 0),并且不会从增量指针中读取数据(因此不会有未定义的行为)

但是@JonathanLeffler在他的评论中所举的例子更短,更容易理解并且更加惯用。它确实做了同样的事情,而没有声明命名的临时变量:

void copyString(char *to, char *from)
{
    while ((*to++ = *from++) != '\0')
       ;
}

代码首先复制字符,然后测试复制的值(因此将复制'\0'),但从未取消引用增量指针(因此没有未定义的行为)。

答案 1 :(得分:0)

发布的代码在遇到NUL字节时停止循环,而不是在此之后停止循环。

关于:

}while(*from);

建议在该行之后加上

*to = '\0';