为什么 strcpy 在使用大小为 1 的目标字符串时省略源字符串中的第一个字符?

Why does strcpy omit the first character in source string when using destination string of size 1?

在 C 中,strcpy 函数用于将源字符串复制到目标字符串中。

但是当我使用大小为 1 的目标 char 数组时,strcpy 正确地将源复制到目标中。但它也会更改源 char 数组。我想了解这在 C 中是如何工作的。

我已经对如何在程序中正确使用 strcpy 进行了一些研究,但它们都使用大于 1 的目标大小。我使用等于 1 的目标大小来编写程序。这就是问题所在.

char a[] = "String ABC";
char b[1];

strcpy(b, a);
int i;
// printf("%c\n", *(&(a[0])-1));

printf("%s\n",a);
printf("%s\n",b);

我希望输出是

String ABC
String ABC

但我得到的输出是

tring ABC
String ABC

您无法将a复制到b,因为b中的space不足。 strcpy 函数将简单地写入数组末尾,这是未定义的行为。这意味着该程序可以以任何不可预测的方式运行(有时,如果您不走运,这意味着它会按您的预期运行)。

换句话说:当您使用strcpy时,您必须确保目标缓冲区足够大,包括空终止符。在此特定示例中,这意味着 b 必须至少有 11 个元素长(10 个用于字符串,1 个用于空终止符)。

正如@Acorn 在他的回答中提到的,您看到的行为是未定义的行为,这意味着编译器可以自由生成任意代码。

但是,如果您想调查这里发生了什么(纯粹出于好奇),它可以帮助打印出数组的地址。

#include <stdio.h>
#include <string.h>

int main(){
    char a[] = "String ABC";
    char b[1];

    strcpy(b, a);
    int i;
    // printf("%c\n", *(&(a[0])-1));

    printf("%s\n",a);
    printf("%s\n",b);

    printf("%p\n",a);
    printf("%p\n",b);
}

在我的机器上,输出如下。

ring ABC
String ABC
0x7ffc36f1b29d
0x7ffc36f1b29c

如您所见,两个数组指针仅相差一个。当您将源复制到目标时,您已经用源数组的最后 N-1 个字符覆盖了源数组的前 N-1 个字符,其中 N 是数组中的字符数源,包括空终止符。

C 不执行边界检查,会让您超出缓冲区的边界。实际行为未定义,但在您的情况下,内存排列可能是这样的:

 b a
|-|S|t|r|i|n|g|A|B|C|[=10=]|

之后strcpy()

 b a
|S|t|r|i|n|g|A|B|C|[=11=]|[=11=]|

所以 b 包含 'S' 并且没有 nul 终止符(因为没有空间),所以当你打印它时,它会遇到 a 其中有 "tringABC" .

其他结果可能取决于编译器如何排序和对齐相邻变量,以及实现如何处理重叠的strcpy()源和目标,这也是未定义的。

问题是您正在将更长的字符串复制到 1 字节字符串,从而导致未定义的行为。

如果你运行这个程序:

#include<stdio.h>
#include<string.h>

int main(int argc, char *argv[])
{
    char a[] = "String ABC";
    char b[1];
    printf("%p\n", &a);
    printf("%p\n", &b);

    strcpy(b, a);
    int i;
    printf("%c\n", *(&(a[0])-1));
    printf("%c\n", a[0]);
    printf("%s\n",a);
    printf("%s\n",b);
    printf("%p\n", &a);
    printf("%p\n", &b);
}

你看到 ba 有连续的地址并且 b 存储在 a 之前的内存地址中。最有可能的是 strcpy 将字符串复制到 b 但由于 b 没有分配来存储这么长的字符串,它会覆盖下一个似乎是 a 的连续存储单元。

让我用||表示一个存储字符的存储单元。假设 -b- 是存储一个字符长字符串的单元格。 在复制之前你有

|-b-|---a memory allocation--|
|-b-|S|t|r|i|n|g| |A|B|C|D|\n|

现在 a 被复制到 b:第二个单元格是 a 的单元格,现在包含 t

  |--a memory allocation-|
|S|t|r|i|n|g| |A|B|C|D|\n|

我想这就是正在发生的事情。但请记住,将较长的字符串复制到较短的字符串中会导致未定义的行为。

有趣的是,我的编译器行为不同:编译时发出警告:

% gcc strcpy.c -O3
In file included from /usr/include/string.h:494:0,
                 from strcpy.c:1:
In function ‘strcpy’,
    inlined from ‘main’ at strcpy.c:8:5:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:90:10: warning:
         ‘__builtin___memcpy_chk’ writing 11 bytes into a region of size 1 overflows the
         destination [-Wstringop-overflow=]
   return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

当我 运行 程序时,它中止了:

% ./a.out                       
*** buffer overflow detected ***: ./a.out terminated