当 while 循环读取 C 中的字符串时,它什么时候停止?

When does a while loop stop when it reads a string in C?

我正在尝试自己实现 strcpy 功能。原始 strcpystring.h 库的一部分。

char *strcpy(char *dest, const char *src)
{
    assert(dest != NULL && src != NULL);
    char *temp = dest;
    while (*src)    
    {
        *dest = *src;
        src++;
        dest++;
    }
    return temp;
}
            
void strcpyTest()
{
   char source[20] = "aaaaaa";
   char dest1[20] = "bbbbbbbbb";
   char desta[10]="abcd";
   puts(dest1); // bbbbbbbbb
   strcpy(dest1, source);
   puts(dest1); // aaaaaa
   strcpy(desta, source);
   puts(desta); // aaaaaa
   strcpy(desta, dest1);
   puts(desta); // aaaaaa
   strcpy(dest1, desta);
   puts(dest1); // aaaaaa
   strcpy(source, desta); 
   puts(source); // aaaaaa
}

如您所见,即使第一次调用 dest 长于 src 的函数也会给出正确的结果,尽管按照逻辑,它应该给出 aaaaaabb 而不是 aaaaaa:

   char source[20] = "aaaaaa";
   char dest1[20] = "bbbbbbbbb";
   strcpy(dest1, source);
   puts(dest1);
   /** aaaaaa **/

为什么我的功能有效?我想我必须在 while (*src)` 退出后在 *dest* 的末尾手动添加 /0 字符。

我的意思是这个 while (*src) 的全部意义在于当它到达 *src* 的末尾时退出,这是字符串中的最后一个字符 /0。 因此,我想我必须自己将这个字符添加到 *dest* 但代码以某种方式工作并复制字符串而无需手动添加 /0.

  1. 所以我的问题是它为什么以及如何仍然有效?

  2. 当我创建一个新数组时,可以说 int *arrchar *arr,共 10 个,即 char arr[10]int arr[10],我只初始化2 个第一个索引,其余索引中的值会发生什么变化?它们会被零或垃圾值填充还是什么?

也许我的代码有效是因为它填充了零,这就是 while 循环停止的原因?

更正此函数不会在目标字符串末尾添加[=11=]。您需要向 dest.

添加最终的 [=11=] 赋值

为什么它似乎按原样工作? 它“有效”是因为 dest 的初始化恰好将 [=11=] 字符放在字符串的正确位置。这些案例实在是“倒霉”,因为它们隐藏着各种各样的问题。另一种可能发生这种情况的情况是,如果您是 运行 调试版本,其中内存自动设置为 0,因此隐藏了最终设置错误。

So my question is why and how it still works?

如果您初始化数组的前 2 个值,则其余值将被视为垃圾,同样与您所说的完全一样。例外情况是数组是“全局”或“静态”的。在这些情况下,编译器会为您将它们设置为 0。

对于初学者,您应该 select 另一个名称而不是 strcpy。

让我们逐步考虑您函数的所有调用。

变量source声明为

char source[20] = "aaaa";

这个声明等同于下面的声明

char source[20] = 
{ 
    'a', 'a', 'a', 'a', '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]', 
    '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]', '[=11=]'
};

因为根据 C 标准,未显式初始化的数组元素由零隐式初始化。

变量desta声明为

char desta[10]="abcd";

这个声明等同于下面的声明

char desta[10]= { 'a', 'b', 'c', 'd', '[=13=]', '[=13=]', '[=13=]', '[=13=]', '[=13=]', '[=13=]' };

所以第一个电话

strcpy(desta, source);

只需用四个字符 "abcd" 替换四个字符 "aaaa"。结果数组 desta 将包含一个字符串,因为下面的终止零被覆盖。

这次通话后

strcpy(desta, dest1);

数组 desta 将包含字符串 "bbbbbbbbb",因为数组 desta 的最后一个零字符未被此调用覆盖。

这次通话

strcpy(dest1, desta);

实际上是数组dest1没有被改变。

在本次通话中

strcpy(source, desta);

因为数组 source 的所有零字符都没有被覆盖,所以数组将包含一个字符串。

如果您先调用,您可能会得到不可预知的结果

strcpy(desta, dest1);

然后

strcpy(desta, source);

因为您的函数没有将终止零附加到目标数组。

这是一个演示程序。

#include <stdio.h>
#include <assert.h>

char * my_strcpy(char *dest, const char *src)
{
    assert(dest != NULL && src != NULL);
    char *temp = dest;
    while (*src)    
    {
        *dest = *src;
        src++;
        dest++;
    }
    return temp;
}

int main(void) 
{
    char source[20] = "aaaaaa";
    char dest1[20] = "bbbbbbbbb";
    char desta[10]="abcd";
    
    my_strcpy(desta, dest1);
    my_strcpy(desta, source);
    
    puts( desta );

    return 0;
}

程序输出为

aaaaaabbb

desta 包含字符串 "aaaaaabbb" 而不是字符串 aaaaaa.

更新后的函数可能如下所示

char * strcpy(char *dest, const char *src)
{
    assert(dest != NULL && src != NULL);
    char *temp = dest;

    while ( ( *dest++ = *src++ ) );    

    return temp;
}

您的数组用零填充,相当于空终止符 '[=10=]'。当您初始化一个数组时,任何未显式设置的元素都将以与静态变量相同的方式设置,也就是说未显式初始化的元素将被隐式初始化为 0。因此在这种情况下,您的字符串会发生在完成复制后有一个空终止符,因为当你初始化数组时,所有未由你的初始化程序显式设置的值都设置为 0.

如果您将一个 4 字符的字符串复制到一个包含 8 字符的字符串的缓冲区中,您只会看到目标字符串中的前 4 个字符发生了变化,而在您命中 null 之前,另外 4 个字符仍然存在-终结者。

来自C11标准工作草案 6.7.9 p21

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

所以根据上面的段落,因为你初始化了数组的一些元素,所以你没有显式初始化的元素将被视为与静态存储持续时间对象一样。 所以对于 statuc 存储持续时间的规则,我们有 6.7.9 p10:

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, then:

— if it has pointer type, it is initialized to a null pointer;

— if it has arithmetic type, it is initialized to (positive or unsigned) zero;

— if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;

— if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;

上面这段话告诉我们,聚合的每个成员(在本例中是一个数组)都将按照元素的规则进行初始化,在本例中,它们的类型是char,这被认为一种算术类型,规则规定其将被初始化为 0。