gcc和Turbo C的输出差异

14

为什么使用两个编译器gccturbo c编译代码时,输出结果存在差异。

#include <stdio.h>

int main()
{    
    char *p = "I am a string";
    char *q = "I am a string";

    if(p==q)
    {
        printf("Optimized");
    }
    else{
        printf("Change your compiler");
    }
    return 0;
}

我在gcc编译器上得到了"Optimized",但在turbo c编译器上得到"Change your compiler"。为什么?


27
听从暗示,使用gcc ;-) - Amarghosh
1
既然你的问题的答案已经包含在 printf 字符串中了(顺便说一下,那里缺少 \n),我猜想这个代码示例是从哪里得到的?这是作业吗? - Jens Gustedt
1
如果你在谈论 Borland 的老编译器,值得一提的是它有一个命令行选项(-d),可以合并字符串常量。 - clstrfsck
5
实际上,Turbo C是一款相当不错的编译器,带有高效的集成开发环境、一般水平的项目管理和可用的调试器。它可以生成漂亮、清晰的.COM或实模式.EXE可执行文件,这非常好,因为嵌入式80x86/ISA或PC-104平台仍在工业应用中使用。如果你还需要为MS-DOS开发,那么它是一个绝佳的选择。 - Nordic Mainframe
2
@Luther Blissett:我认为OP不需要开发MS-DOS。这个问题看起来像是作业/初学者的东西。在这种情况下,编译器可以生成适用于新平台的代码,可能是更好的选择。 - SigTerm
Turbo C是一种过时的编译器,但在印度仍然是进行C编程的标准。顺便说一句,我认为这是由于指针差异造成的。当谈论内存地址时,*p*q并不意味着相同的含义! - user9258013
7个回答

32

你的问题已被标记为C和C++。因此我将为这两种语言都回答。

[C]

来自ISO C99 (Section 6.4.5/6)

未指定这些数组是否不同,只要它们的元素具有适当的值即可。

这意味着pq是否指向同一个字符串文字是未指定的。在gcc的情况下,它们都指向"I am a string"(gcc优化了您的代码),而在turbo c中它们不是。

未指定的行为: 使用未指定的值或其他行为,其中国际标准提供了两个或多个可能性,并且对于任何实例都没有进一步要求选择哪个


[C++]

来自ISO C++-98 (Section 2.13.4/2)

所有字符串文字是否不同(即存储在不重叠的对象中)是实现定义的。

在C++中,您的代码会调用实现定义的行为。

实现定义的行为: 未指定的行为,其中每个实现记录如何进行选择


另请参见问题。


7
感谢您报告了“未指定”/“实现定义”行为的主观标准定义意义,给您点赞。 - ShinTakezou

14

由于您的字符串字面量是常量表达式,即您不应该通过指针修改它,因此将其存储在内存空间中两次没有实际意义。作为一个较新的编译器,gcc默认合并这些常量,而Turbo C则不会。这标志着gcc支持新语言标准的迹象,该标准具有const数据的概念。


2
通过传递-fno-merge-constants选项,您可以在GCC中覆盖此行为,尽管通常没有好的理由这样做。 - Hasturkun
1
@Amardeep,你的答案并不完全正确。字符串字面值不是常量表达式,否则就不可能将其分配给char*。确实,通过指针访问时不应更改它,但是允许这样做。行为只是未定义的...无论如何,我不明白为什么有人会分配这样的任务,展示出如此糟糕的习惯。这应该始终是一个char const*,将字符串字面值的地址分配给它。 - Jens Gustedt
@Jens:早期的C编译器没有const的概念,因此即使针对ROM的编译器在程序加载时将字符串保留在只读内存中而不是复制到RAM中,你也只能将其分配给char *。为了可移植性,将它们视为不可变总是更安全的。新的编译器肯定将它们视为不可变,否则默认合并行为将是不安全的。 - Amardeep AC9MF
1
由于您的字符串字面值是常量表达式,即您不允许通过指针修改它。但是,“常量表达式”这个术语可能会与正式概念混淆。在C++和C中,“常量表达式”意味着可以在编译时确定表达式的某些特征(例如:其值(例如:整数和整数常量表达式),其引用地址(例如:地址和引用常量表达式)以及其成员偏移量(例如:成员指针常量表达式))。 - Johannes Schaub - litb
实际上,现在我想想,既然它未定义,我既不会说它是被允许的,也不会说它是被禁止的。但是只要将其保持为“未定义”,因为这取决于实现来决定(甚至不必决定!)。 - Johannes Schaub - litb
显示剩余4条评论

11

请忘记与

"这是因为Turbo C太老了,他们当时无法做到,因为它必须很快,但GCC是全新的和超酷的,所以它才能做到!"

两个编译器都支持将字符串常量合并作为一个选项。 GCC选项(-fmerge-constants)在优化级别上被打开,而Turbo C选项(-d)默认情况下被关闭。如果您正在使用TCC IDE,则转到 选项|编译器... |代码生成... 并选中"重复字符串合并 "。


1
我发现你的回答很难读,最初完全误解了,因为引用并不是非常清楚地可识别。我希望你能接受我的格式更改。除此之外,对于仍在处理 TC 的任何人来说,这是好的和有用的信息,所以:+1。 - Carl Smotricz
哦,这好多了。谢谢! - Nordic Mainframe
我很欣赏这样一份写得很好且公正无私的回答。 - undefined

5

从gcc手册页中:

-fmerge-constants

尝试在编译单元之间合并相同的常量(字符串常量和浮点常量)。

如果汇编器和链接器支持,则此选项是优化编译的默认选项。使用-fno-merge-constants来禁止此行为。

在级别-O,-O2,-O3,-Os启用。

因此输出。


3
Turbo C被优化为快速编译,因此它没有任何会使其变慢的功能。即使只是轻微地识别重复字符串也会减慢速度。

4
我认为这个解释是错误的。Turbo C 的默认设置只是为了允许默认情况下修改字符串常量的破损代码正常工作。 - R.. GitHub STOP HELPING ICE

1

编译器可能会保留两个相同的文字常量,如果它认为这是合适的。找出是否存在这种情况,可能是这个程序的目的。

在早期,汇编器将所有文字常量保存在一个文字池中,而修改程序中的“常量”则是一种被认可(但不被批准)的技术。

如果编译器在这种情况下允许 *p = 'H';,那么行为上会产生重要的差异。


应该说,在许多早期(ANSI之前)的C版本中,允许修改文字字符串。 - JeremyP
@JeremyP:定义“允许”。我相当确定它一直是未定义的行为(嵌入式系统可能已经将该字符串放在ROM中)(尽管在ANSI之前,从技术上讲,每件事都被正式称为“未定义的行为”)。 - James Curran
嵌入式系统的编译器通常会给用户非常细粒度的控制权,让他们可以自由安排代码的存储位置。字符串字面量不太可能被放在只读存储器(ROM)中而使你无能为力。 - Nordic Mainframe
在 K&R C 中,“允许”指的是虽然没有明确规定,但你可以隐式地更改文字字符串。一些编译器甚至包含从文本段复制字符串字面量到数据段作为程序初始化的一部分的代码。 - JeremyP
@Luther:你总是可以采取正确和可移植的方式来解决这个问题。char mystring[] = "literal goes here"; 然后使用 mystring 而不是 "literal goes here" - R.. GitHub STOP HELPING ICE
一些嵌入式系统可能只有8K的ROM和256字节或更少的RAM。 我认为字符串常量理论上可以放在RAM中,但这似乎相当危险。 - supercat

0
历史注解:由于地址比浮点数常量小,FORTRAN曾经处理浮点常量的方式很像C处理字符串。由于内存宝贵,相同的常量将被分配相同的空间。此外,参数传递总是通过引用进行的。这意味着如果将一个数字常量传递给修改其参数的过程,则该“常量”的其他出现将更改值。
因此有句老话:“变量不会变;常量不是常数。”
顺便说一下,有没有人注意到Turbo C 2.0 printf中的错误,当使用类似“%1.1f”的格式来打印像99.99这样的数字时会失败(输出00.0)?在2.01中修复了这个问题,这让我想起了Windows 3.1计算器的错误。

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