C中的字符串声明

3
“下面展示的Code 1和Code 2有什么区别?我在两种情况下都得到了相同的输出。内部是否有任何不同?”

Code 1

char test[30]="KEL";
strcat (test,"DATA");

代码 2

char test[]="KEL"
strcat (test,"DATA");

1
第一段代码将打印出“Hello World!”。第二段代码将在编译时产生错误。 - Rahul
4
@VforVendetta,你使用哪个编译器和编译标志来得到第二段代码的编译错误? - R Sahu
一旦在 Turbo C3 中检查。我刚刚检查过并告诉你。 - Rahul
@VforVendetta - 那个编译器太古老了。如果它警告有关缓冲区溢出,那是它今天仅剩的一点优势。我建议你换成一个更现代化的编译器。 - StoryTeller - Unslander Monica
非常感谢!但是对我来说它有效,我在IE上测试了我的网站 :p - Rahul
4个回答

6
在你的第一个代码示例中,test 将有 30 个字符的空间。
前四个字符将是字符串 "KEL" 和空终止符(如果我没记错的话,剩下的字符都将被初始化为零,感谢 chux)。连接 "DATA" 到它上面是良好定义的。
在第二个示例中,它仅有 4 个字符的空间,因为缓冲区的长度是由字符串文字推导出来的。当你将 "DATA" 连接到它上面时,你将超出缓冲区的边界。这是未定义的行为。
如果行为明确地被标准定义为未定义的,就像这里一样,你的编译器和运行时实现可以做任何事情。你的程序可能会崩溃。或者它可能会被制成运行邪恶代码。它也可能会像你的情况一样 看起来 工作。但你不能依赖这一点。

更明确地说,第二个示例代码是非法的。 - John Zwinck
1
在第一种情况下,我认为test的余数也被设置为零 - 在C语言中不存在部分初始化。 - chux - Reinstate Monica
3
取决于您对“合法”或“非法”的理解...... 这不是一个约束违规,但它保证了未定义的行为,因此如果执行总是到达此点,则编译器可能会拒绝程序。 - M.M
@StoryTeller:从语法上讲是合法的,但在“非法内存访问”的意义上是非法的。我简直不敢相信我要解释这个。 - John Zwinck
@JohnZwinck - 非法内存访问是一个实现术语。C标准将其称为“未定义行为”,但代码本身是合法的。但我认为我们都明白我们所指的不同,并且在整个问题上达成了一致。 - StoryTeller - Unslander Monica
@JohnZwinck - 严格来说,非法内存访问可能不会发生。如果我有几个相邻的缓冲区,写操作可能是对进程拥有的有效内存的写入。这种不可预测性是我认为将某些东西称为UB是最糟糕的原因。 - StoryTeller - Unslander Monica

2

让我们从C标准开始。根据它(6.7.9 Initialization)

14个字符类型的数组可以通过字符字符串文字或UTF-8字符串文字进行初始化,可选地用大括号括起来。字符串文字的连续字节(包括终止空字符,如果有足够的空间或数组大小未知)将初始化数组的元素。

19初始化应按初始化程序列表顺序进行,为特定子对象提供的每个初始化程序都会覆盖同一子对象的先前列出的初始化程序;151)所有未明确初始化的子对象都将隐式初始化为具有静态存储期的对象。

这些引用如何应用于字符数组初始化?

在此代码片段中

char test[30]="KEL";
strcat (test,"DATA");

数组test声明了30个元素。数组的前四个元素通过字符串字面量中的元素显式初始化,可以表示为{'K','E','L','\0'}。所有其他元素都像具有静态存储期的对象一样隐式初始化,即通过值'\0'

因此,在其声明后,数组test内部看起来像

{ 
'K', 'E', 'L', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0' 
}

在这个第二段代码片段中
char test[]="KEL"
strcat (test,"DATA");

这里声明了一个大小未知的数组,其大小根据初始值的数量计算。由于字符串字面量有四个字符{'K','E','L','\ 0'},因此数组将恰好具有相同的四个字符。

在第一个代码片段中,您可以更改具有值'\ 0'的数组元素。例如,您可以编写

test[3] = 'D';
test[4] = 'A';
test[5] = 'T';
test[6] = 'A';

数组将会是这个样子

{ 
'K', 'E', 'L', 'D', 'A', 'T', 'A', '\0', '\0', '\0', '\0', '\0', '\0', 
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0' 
}

这是因为结果字符串将包含字符串"KELDATA"。与多个赋值语句相比,更简单的写法是:
strcat (test,"DATA");

并能获得相同的结果。
但是第二段代码片段呢?正如你所见,除了由字符串字面值 "KEL" 初始化的元素之外,它没有任何 "自由" 元素。因此,你只能更改它的四个元素,不能向存储在数组中的字符串字面量追加新数据,只能覆盖它们。
因此,这个语句。
strcat (test,"DATA");

这将导致覆盖数组以外的内存,程序会出现未定义的行为。


1
char test[30]="KEL";

在这种情况下,编译器在堆栈中分配了30个字节。只要最终字符串的大小不超过30个字节(包括空终止符),就可以使用strcat连接字符串。

char test[]="KEL"

在这种情况下,编译器本身决定为给定的字符串分配足够的内存。它是3个字节加上'\0'(空终止符),所以即使再添加一个字符也没有足够的空间。
尝试写入超出编译器分配的范围将导致未定义的行为,正如StoryTeller已经说明的那样,这意味着程序可能会正确执行,也可能会崩溃。或者有时候两者都会发生。未定义的行为也是非常特定于编译器的属性。由gcc、clang或任何其他编译器编译的完全相同的源代码可能会产生不同的结果。
注意:字符类型(char)的大小为1个字节。

-1

代码1 char test[30]="KEL";// 你不能输入超过30个字符的值,但少于30个是可以的。

代码2 char test[]="KEL" //你可以自由输入任何大小的值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接