为什么 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);
}
你看到 b
和 a
有连续的地址并且 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
在 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);
}
你看到 b
和 a
有连续的地址并且 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