【问题标题】:Different outputs for almost same programs in CC中几乎相同程序的不同输出
【发布时间】:2017-03-16 18:10:02
【问题描述】:

样本 1:

char a []={'h','i'};
int i;
for(i=0;a[i]!='\0';i++){
printf("%c",a[i]);
}
printf("%s",a);

输出:hi☻hi♥

示例 2:

char a []={'h','i'};
int i;
for(i=0;a[i]!='\0';i++){
    char l = a[i];
    printf("%c",a[i]);
}
printf("%s",a);

输出:hii♥hi♥♦

示例 3:

char a [5]={'h','i'};
int i;
for(i=0;a[i]!='\0';i++){
printf("%c",a[i]);
}
printf("%s",a);

输出:嗨

为什么这三个程序的输出不一样?

示例 1 和示例 2 几乎是相似的代码,只是多了一行 char l = a[i],并且示例 3 根据数组大小的声明不同于示例 1 和示例 2。

【问题讨论】:

  • 将无效参数传递给printf(非终止字符串而不是终止)会导致未定义的行为

标签: c arrays string char printf


【解决方案1】:

对于示例 1 和 2,您通过将非空终止数组作为参数传递给 printf() 中的 %s 来调用 undefined behavior

对于类似的定义

 char a []={'h','i'};

a 将被分配内存以仅保存两个元素,不会分配额外空间来存储终止 null,在这种情况下使用大括号括起来的初始化列表。

引用第 §7.21.6.1 章,将 %s 格式说明符与 printf() 系列一起使用,

s
如果不存在 l 长度修饰符,则参数应为指向初始值的指针 字符类型数组的元素。280)数组中的字符是 写到(但不包括)终止空字符。如果 指定精度,写入的字节数不超过。 如果 精度未指定或大于数组的大小,数组应 包含一个空字符。

OTOH,在示例 3 的情况下,对于类似的定义

char a [5]={'h','i'};

数组以空值结尾的,所以输出是正确的。在这种情况下,数组以空值结尾,因为您在声明时提供了数组大小并在大括号括起来的列表中提供了较少数量的初始化器,因此剩余元素初始化为0(好像它们有static 存储)。相关,C11,第 6.7.9 章,(强调我的

如果大括号括起来的列表中的初始值设定项少于元素或成员的数量 用于初始化已知数组的字符串文字中的聚合或更少字符 大小超过数组中的元素,聚合的其余部分应为 隐式初始化与具有静态存储持续时间的对象相同。

【讨论】:

  • @AnimeshKumarPaul 因为 UB,请在我的回答中查看引用的链接。
【解决方案2】:

在 C 中,数组只有大小,没有终结符。因此,两个字符的数组(如您的前两个示例)将包含您指定的两个字符,仅此而已。当你循环寻找“终结者”时,你会越界并有未定义的行为

第三种情况不同,因为你定义了一个包含五个元素的数组,但只初始化了前两个。然后,C 标准要求将数组的其余部分初始化为零,这与字符 '\0' 相同。第三个示例中的数组仍然没有明确的终止符,只是碰巧其余部分被初始化为与字符串终止符相同的值。

【讨论】:

    【解决方案3】:

    要使printf("%s",a) 工作,a 指向的内存块必须以 0 结尾。

    for (i=0; a[i]!='\0'; i++) 开头的代码也是如此。

    在您的所有示例中,此内存块都以 'i' 结尾,而不是 0。

    您可以通过将a 的初始化更改为以下任一方法来修复它:

    • char a[] = {'h','i',0};
    • char a[] = {'h','i','\0'};
    • char a[] = "hi";
    • char *a = "hi";

    【讨论】:

    • 请注意前三个选项是等价的,而在最后一个选项中,a指向的内存块的内容是不可修改的。
    猜你喜欢
    • 2019-04-21
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2018-07-08
    • 1970-01-01
    • 2015-04-28
    相关资源
    最近更新 更多