问题描述
我正在尝试自己实现 strcpy
函数。原始 strcpy
是 string.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 **/
为什么我的函数有效?我猜想我必须在 /0
(*src)` 退出后在 *dest*
的末尾手动添加 while
字符。
我的意思是这个 while (*src)
的全部意义在于当它到达 *src*
的末尾时退出,这是字符串中的最后一个字符 /0
。
因此,我想我必须自己将这个字符添加到 *dest*
中,但代码以某种方式工作并复制字符串而无需手动添加 /0
。
-
所以我的问题是它为什么以及如何仍然有效?
-
当我创建一个新数组时,让我们说
int *arr
或char *arr
,共 10 个,即char arr[10]
或int arr[10]
,我只初始化前 2 个索引,其余索引中的值会发生什么变化?它们会被零或垃圾值填充还是什么?
也许我的代码有效是因为它填充了零,这就是 while
循环停止的原因?
解决方法
对于初学者,您应该选择另一个名称而不是 strcpy。
让我们逐步考虑您的函数的所有调用。
变量source
声明如下
char source[20] = "aaaa";
这个声明等价于下面的声明
char source[20] =
{
'a','a','\0','\0'
};
因为根据 C 标准,未显式初始化的数组元素被隐式初始化为零。
变量 desta
声明如下
char desta[10]="abcd";
这个声明等价于下面的声明
char desta[10]= { 'a','b','c','d','\0' };
所以第一次调用
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;
}
,
更正此函数不会在 dest 字符串的末尾添加 \0
。您需要将最终的 \0
分配添加到 dest。
为什么它似乎按原样工作?
它“有效”,因为您对 dest
的初始化恰好在字符串的正确位置放置了一个 \0
字符。这些案例老实说是“倒霉”,因为它们隐藏了各种各样的问题。另一种可能发生这种情况的情况是,如果您正在运行一个调试版本,其中内存自动设置为 0,因此最终设置的错误被隐藏。
So my question is why and how it still works?
如果你初始化一个数组的前 2 个值,其余的都被认为是垃圾,正如你所说的那样。例外情况是数组是“全局的”或“静态的”。在这些情况下,编译器会为您将它们设置为 0。
,您的数组用零填充,这相当于空终止符 '\0'
。初始化数组时,任何未显式设置的元素都将像静态变量一样设置,也就是说未显式初始化的元素将被隐式初始化为 0。因此在这种情况下,您的字符串会发生完成复制后有一个空终止符,因为在初始化数组时,所有未由初始化程序显式设置的值都设置为 0
。
如果您将一个 4 个字符的字符串复制到一个包含一个 8 个字符的字符串的缓冲区中,您只会看到目标字符串中的前 4 个字符发生了变化,而在遇到空终止符之前还有另外 4 个字符仍然存在.
来自 C11 标准工作草案 6.7.9 p21
如果括号括起来的列表中的初始值设定项少于元素或成员的数目 聚合或字符串文字中用于初始化已知数组的更少字符 尺寸大于数组中的元素,聚合的剩余部分应为 隐式初始化与具有静态存储持续时间的对象相同。
所以根据上面的段落,因为你初始化了数组的一些元素,你没有显式初始化的元素会被当作静态存储持续时间对象来对待。 所以对于 statuc 存储持续时间的规则,我们有 6.7.9 p10:
如果没有显式初始化具有自动存储期的对象,则其值为 不定。 如果一个具有静态或线程存储持续时间的对象没有被初始化 明确,然后:
——如果是指针类型,则初始化为空指针;
——如果是算术类型,则初始化为(正或无符号)零;
——如果是聚合,则根据这些规则(递归地)初始化每个成员, 并且任何填充都被初始化为零位;
——如果是联合,则根据这些(递归地)初始化第一个命名成员 规则,并且任何填充都被初始化为零位;
上面的段落告诉我们,聚合的每个成员(在这种情况下是数组)都将按照元素的规则进行初始化,在这种情况下,它们的类型为 char
,这被认为是算术类型,规则规定将其初始化为 0。