【问题标题】:Different behavior of printf() in Turbo C and gcc when trying to print a pointer尝试打印指针时 Turbo C 和 gcc 中 printf() 的不同行为
【发布时间】:2015-05-13 03:36:29
【问题描述】:

下面的代码给出了 Turbo C 编译器中没有错误的输出,并给出了变量的地址和它的值:

int a=5,*fp;
fp=&a;
printf("%d %d\n",fp,*fp);

但是当我在 Linux 中使用 GCC 编译器编译相同的代码时,它会报错:

`warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
printf("%d %d\n",fp,*fp);`

但与 %p 格式说明符相同的代码在 GCC 编译器中工作,我同意这一点。问题是:它是如何在 Turbo C 平台上运行的?

附:问题不在于(在 Turbo C 中)未报告错误,而是在 Turbo C 上 它给出了一个有符号整数值,在重复执行程序时不会改变;会是垃圾吗?

P.P.S Turbo C 在 MSDOS 平台上运行,而 GCC 在 64 位 Linux 上运行,如果有帮助的话。

【问题讨论】:

  • 未定义的行为不一定会导致您的编译器报告错误或警告。但这并不意味着您的代码被破坏的更少。
  • Gcc 不会发出错误。它发出一个警告,这根本不是一回事。当 Turbo C 不能识别和诊断 真正 问题时,它能够识别和诊断问题是实施质量的问题。
  • “代码实际打印内存位置的值”是什么意思?如果您说这是关于输出的,请显示一些输出示例。
  • 它给出了一个有符号整数值,即使我反复执行程序,该值也没有改变,这意味着它不是垃圾?
  • printf("%d",fp,*fp);printf("%d %d\n",fp,*fp); 不是“相同的代码”

标签: c gcc turbo-c


【解决方案1】:

printf() 假设您将为 %d 说明符传递一个 signed int

在您使用的平台和编译器上,整数和指针的大小可能相同,printf() 能够正确显示。您的指针被重新解释为 int。

在不同的平台上编译和运行它,例如,int 是 32 位,指针是 64 位(例如 x64 上的 gcc),将是未定义的行为。如果你足够幸运,它会崩溃。如果没有,你会得到垃圾。

【讨论】:

  • 我想也许这回答了我的问题
  • 给它一些时间。你可能会得到一个更好的。
  • 这里不知道还能说什么。不明白的地方能具体点吗?
  • 在 John Bollinger 的回答中提到,许多 64 位平台的 C 实现都具有 32 位整数和 64 位指针。
  • 这正是我在第三段第一句所说的。
【解决方案2】:

%d 转换说明符要求相应参数的类型为 [signed] int。如果相应的参数实际上具有不同的类型,则该行为是明确未定义的。无论预期类型和实际类型的相对大小如何,无论这些类型之间是否可以进行隐式或显式转换,情况都是如此。

当程序表现出未定义的行为时,既不定义编译器的行为,也不定义任何生成的编译程序的任何部分的行为。诊断问题不需要 Turbo C。另一方面,gcc 完全可以诊断它,甚至可以拒绝源代码并给出错误,而不仅仅是警告你。就 C 而言,如果触发了任何未定义的行为,整个程序的 行为绝对可以是任何东西——从作者的意图(无论可能是什么)到从机器的扬声器发出粗鲁的 cmets,远远不止。

在实践中,如果预期类型和实际类型大小相同,并且转换说明符不是%s,则未定义行为很可能(但绝不肯定)以相对良性的方式表现出来。否则,所有赌注都取消。请特别注意,许多 64 位平台的 C 实现都具有 32 位 ints 和 64 位指针。

【讨论】:

  • “整个程序未定义”只有在保证执行会到达触发 UB 的行时才会发生
  • @MattMcNabb,我已经更新了我的答案,以澄清有关整个程序行为的部分适用于实际触发未定义行为的情况。我认为这在技术上是准确的,尽管这与 UB 被保证触发之间的区别主要是(英语)语义之一,特别是考虑到程序无法预测未来。
【解决方案3】:

每个转换说明符,例如%d,指定所需参数的类型和用于打印它的格式。

  • %d 需要 int 类型的参数(等效于 signed int),并以十进制打印。
  • %u 需要 unsigned int 类型的参数并以十进制打印。
  • %x 需要 unsigned int 类型的参数并以十六进制打印。

等等。

在您的代码中:

int a=5,*fp;
fp=&a;
printf("%d %d\n",fp,*fp);

第二个%d 是正确的,因为对应的参数*fpint 类型。但是第一个是不正确的,因为对应的参数fp 的类型是int*

如果您为转换说明符传递了错误类型的参数,则行为未定义。如果您这样做,编译器不需要警告您,因为在最一般的情况下无法检测到错误(格式字符串不必是字符串文字)。一些编译器,包括 gcc,将分析格式字符串,如果它们是字符串文字并警告不匹配。 Turbo C 显然没有(这并不奇怪,它是一个非常古老的编译器)。

打印指针值的正确格式是%p。这需要void* 类型的参数,并以实现定义的方式打印它。 void* 以外的类型的指针应该被转换。

您的代码的正确版本是:

int a = 5, *fp;
fp = &a;
printf("%p %d\n", (void*)fp, *fp);

【讨论】:

    【解决方案4】:

    这是一个警告,而不是错误。第一个编译器也可以这样说,但不是。

    警告不会停止编译,所以它也会起作用。它们只是不同的编译器。另外,编译器接受你的程序并不意味着程序是正确的。

    【讨论】:

    • 警告不会停止编译,但结果程序是否在任何合理的意义上“工作”是字面上未定义的。如果在具有 32 位 ints 和 64 位指针的系统上构建和运行 64 位应用程序,很可能会产生与预期不同的行为,这在今天很常见。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-07
    • 1970-01-01
    • 1970-01-01
    • 2020-01-10
    相关资源
    最近更新 更多