【问题标题】:Why the string initialized with the too long initiializer would behave like this为什么用太长的初始化程序初始化的字符串会像这样
【发布时间】:2016-12-09 06:55:24
【问题描述】:
#include<stdio.h>

void main()
{
    int i;
    char str[4] = "f4dfkjfj";
    char str2[3] = "987";
    char str3[2] = {'j','j','\0'};

    //printf("%c\n",str[1]);
    //printf("%c\n",1[str]);

    puts(str);
    puts(str2);
    puts(str3);           
}

输出观察:

  • 打印str2 打印来自str2str 的内容。
  • 打印str3 打印str3str2str

为什么在打印非 nul 结尾的字符串时会出现这种行为,该字符串与之前定义的字符串连接,直到 puts() 函数遇到 "\0" 字符(此函数打印直到遇到 nul )?

(注意:我故意用太长的初始化字符串来初始化它们)

【问题讨论】:

  • 不应该编译器发出警告甚至错误声明为char str2[3] = "987";吗?
  • 是的,它确实会发出警告,警告没有 nul 字符的初始化程序过长,但后来我故意这样做是为了学习..
  • 关于str2的情况,see this

标签: c string pointers


【解决方案1】:

(注意:我故意用太长的初始化字符串来初始化它们)

那么你也应该注意副作用。

所有数组的问题是,它们不是以空值结尾的,所以它们是不是 字符串

引用C11,第 7.1.1 章,术语定义,(强调我的

字符串是一个连续的字符序列以第一个null结尾并包括 字符。 [...]

将它们与字符串处理函数(如puts())一起使用会调用undefined behavior,因为这些函数在搜索空终止符时会超出范围(即,超出允许范围)内存区域)并导致无效的内存访问

再次引用标准,第 7.21.7.9 章,

puts函数将s指向的字符串写入stdout指向的流, 并将换行符附加到输出。终止空字符不是 写的。

预期的参数是一个字符串,你的代码中没有一个参数是。

也就是说,FWIW,对于托管环境,main() 的推荐签名至少是 int main(void)

【讨论】:

  • 这不是特别未定义的行为...它打印的内容与之前定义的字符串完全相同...首先执行此并查看
  • @PavithranRavichandiran 你能引用一些说这是不是 UB的东西吗?我有兴趣。
  • 我找不到任何东西,这就是我来这里的原因......但我猜如果它的 UB ,那么它会返回其他东西......为什么特别是之前定义的字符串的内容。 .
  • @PavithranRavichandiran 你读过链接的维基页面吗?我猜它有你正在寻找的答案。 :)
  • 不应该编译器发出警告甚至错误声明为char str2[3] = "987";吗?
【解决方案2】:

初始化

  char str[4] = "f4dfkjfj";

还有

  char str2[3] = "987";

  char str3[2] = {'j','j','\0'};

不正确,因为表达式 char str[4] 为数据分配了 4 个字节,但数据 - "f4dfkjfj" 需要 9 个字节 - 8 个字节用于可见字符,另外一个字节用于'\0'

更新:

让我们考虑以下示例

#include<stdio.h>

void main()
{
    int i;
    char str[4] = "f4dfkjfj";
    char str2[3] = "987";
    printf("Address of str2 = %p and size is %d bytes\n", str2, sizeof(str2));
    printf("Addres     |  Data in memory\n");
    char * ptr;
    for (ptr = str2 - 2; ptr <= str2 + 5; ptr++)
    {
        printf("%p   | %c\n", ptr, *ptr);
    }
}

在 Windows 7 下的 Visual Studio 2013 中,我看到以下内容:

但如果我将char str2[3] = "987"; 更改为char str2[4] = "987"; 结果将是

试试puts(str2) 换成char str2[4] = "987";,你会看到不同。

注意:每次内存地址(在堆栈中的局部变量)都(可以)不同,但分配的内存周围的数据(更改与否)更重要。

【讨论】:

  • 不,char str2[3] = "987"; 也不正确,因为多了一个字节 - '\0'。 IE。 char str2[4] = "987"; 是正确的
  • @SouravGhosh 部分同意!将puts() 与错误初始化的数据一起使用会导致输出错误
  • @SouravGhosh str1str3 定义违反约束,str2 不是
  • 这里的重点是 str2 是一个错误,但它是 C 标准(愚蠢地)允许的错误。请参阅@MM 的回答。
  • 无论如何,通过在臭名昭著的糟糕的 Visual 编译器中编译代码来证明 C 的工作原理并不是一个好主意。你所证明的只是 Visual C 没有遵循标准——我们已经知道了。
【解决方案3】:

这些行是违反约束的。编译器应该给出错误信息并且程序的行为是完全未定义的:

char str[4] = "f4dfkjfj";
char str3[2] = {'j','j','\0'};

违反的约束是数组的初始值设定项太多。 (C11 6.7.9/2)

不过char str2[3] = "987"; 是正确的,有一种特殊情况,当数组从字符串字面量初始化时,如果数组中没有空间,则允许忽略空终止符。 (C11 6.7.9/14)

继续将str2 传递给期望以空字符结尾的字符串的函数会导致未定义的行为。

【讨论】:

  • 投票赞成在 6.7.9/14 中提及晦涩的“C 标准错误”。我想很多人都不知道这种特殊情况。
  • @Lundin 我猜它与strncpy 有类似的用例,例如FAT 文件名(8 字节非终止缓冲区,仅当文件名小于 8 个字符时才用 null 填充)
  • 那么为什么他们在下一段中为wchar_t制定了同样的奇怪规则? wchar_t 的“固定宽度字符串”是否曾经存在于 Unix 或其他地方?
猜你喜欢
  • 2010-12-14
  • 2014-10-16
  • 1970-01-01
  • 2019-06-18
  • 2020-05-29
  • 1970-01-01
  • 2014-02-19
  • 2015-06-25
  • 2012-06-24
相关资源
最近更新 更多