为什么我不能以这种方式复制终止空值?

Ker*_*nli 0 c null

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

\n\n
void copyString(char *to, char *from)\n{\n    do{\n        *to++ = *from++;\n    }while(*from);\n}\nint main(void)\n{\n    char to[50];\n    char from[] = "text2copy";\n    copyString(to, from);\n    printf("%s", to);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是代码的输出:

\n\n
text2copy\xc3\x87\xe2\x96\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x95\x91kvu\xc2\xa1lvu\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
text2copy\xc3\x96\xe2\x96\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x95\x91kvu\xc2\xa1lvu\ntext2copy\xe2\x95\xa8\xe2\x96\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x95\x91kvu\xc2\xa1lvu\ntext2copy\xe2\x95\xa1\xe2\x96\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x95\x91kvu\xc2\xa1lvu\n//etc\n
Run Code Online (Sandbox Code Playgroud)\n\n

为什么会发生这种情况?

\n

Jer*_*iah 5

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

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

char from[] = "text2copy";
Run Code Online (Sandbox Code Playgroud)

记忆中是这样的:

            ----+----+----+----+----+----+----+----+----+----+ ----+----
   其他记忆 | t | 电子| x| t | 2 | c | 哦| p| y | \0 | 其他记忆
            ----+----+----+----+----+----+----+----+----+----+ ----+----
                   ^
                 从

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

            ----+----+----+----+----+----+----+----+----+----+ ----+----
   其他记忆 | t | 电子| x| t | 2 | c | 哦| p| y | \0 | 其他记忆
            ----+----+----+----+----+----+----+----+----+----+ ----+----
                                                           ^
                                                         从

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

            ----+----+----+----+----+----+----+----+----+----+ ----+----
   其他记忆 | t | 电子| x| t | 2 | c | 哦| p| y | \0 | 其他记忆
            ----+----+----+----+----+----+----+----+----+----+ ----+----
                                                                ^
                                                              从

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

现在你可能认为这可以解决这个问题:

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

它确实复制了'\0'角色,但仍然存在问题。该代码存在更根本的缺陷,因为正如 @JonathanLeffler 在评论中所说,对于空字符串,您会查看字符串末尾之后的内存内容,并且由于它没有分配给您访问,因此会导致未定义的行为:

            ----+----+----
   其他记忆 | \0 | 其他记忆
            ----+----+----
                   ^
                 从

计算机执行*to++ = *from++;将字符复制'\0'to然后递增两者的操作tofrom这使得从点到您不拥有的内存:

            ----+----+----
   其他记忆 | \0 | 其他记忆
            ----+----+----
                        ^
                      从

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

我在评论中所做的示例建议将复制的值保存到临时变量中:

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

我建议这种特定方式的原因是向您展示问题是您正在测试的内容,而不是之后测试循环条件。如果您保存复制的值,然后稍后测试该保存的值,则在测试之前会复制字符(因此 \0 被复制),并且您不会从递增的指针中读取(因此不存在未定义的行为)

但@JonathanLeffler 在他的评论中举的例子更短,更容易理解,也更惯用。它在不声明命名临时变量的情况下执行相同的操作:

void copyString(char *to, char *from)
{
    while ((*to++ = *from++) != '\0')
       ;
}
Run Code Online (Sandbox Code Playgroud)

代码首先复制字符,然后测试复制的值(因此将'\0'复制),但递增的指针永远不会取消引用(因此不存在未定义的行为)。