为什么这个循环产生"警告:迭代3u调用未定义的行为"并输出超过4行?

zer*_*kms 160 c++ gcc undefined-behavior

编译:

#include <iostream>

int main()
{
    for (int i = 0; i < 4; ++i)
        std::cout << i*1000000000 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

gcc产生以下警告:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^
Run Code Online (Sandbox Code Playgroud)

我知道有一个有符号的整数溢出.

我不能得到的是为什么i价值被溢出操作打破了?

我已经阅读了为什么x86上的整数溢出与GCC导致无限循环的答案,但我仍然不清楚为什么会发生这种情况 - 我认为"未定义"意味着"任何事情都可能发生",但这种特定行为的根本原因是什么?

在线:http://ideone.com/dMrRKR

编译: gcc (4.8)

mil*_*bug 106

有符号整数溢出(严格来说,没有"无符号整数溢出"这样的东西)意味着未定义的行为.这意味着任何事情都可能发生,并讨论为什么它会在C++规则下发生没有意义.

C++ 11草案N3337:§5.4:1

如果在评估表达式期间,结果不是在数学上定义的,或者在其类型的可表示值范围内没有,则行为未定义.[注意:大多数现有的C++实现忽略整数流.除零处理,使用零除数形成余数,所有浮点异常因机器而异,通常可通过库函数调整. - 尾注]

您的代码编译时g++ -O3发出警告(即使没有-Wall)

a.cpp: In function 'int main()':
a.cpp:11:18: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^
a.cpp:9:2: note: containing loop
  for (int i = 0; i < 4; ++i)
  ^
Run Code Online (Sandbox Code Playgroud)

我们分析程序正在做什么的唯一方法是读取生成的汇编代码.

这是完整的装配清单:

    .file   "a.cpp"
    .section    .text$_ZNKSt5ctypeIcE8do_widenEc,"x"
    .linkonce discard
    .align 2
LCOLDB0:
LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  __ZNKSt5ctypeIcE8do_widenEc
    .def    __ZNKSt5ctypeIcE8do_widenEc;    .scl    2;  .type   32; .endef
__ZNKSt5ctypeIcE8do_widenEc:
LFB860:
    .cfi_startproc
    movzbl  4(%esp), %eax
    ret $4
    .cfi_endproc
LFE860:
LCOLDE0:
LHOTE0:
    .section    .text.unlikely,"x"
LCOLDB1:
    .text
LHOTB1:
    .p2align 4,,15
    .def    ___tcf_0;   .scl    3;  .type   32; .endef
___tcf_0:
LFB1091:
    .cfi_startproc
    movl    $__ZStL8__ioinit, %ecx
    jmp __ZNSt8ios_base4InitD1Ev
    .cfi_endproc
LFE1091:
    .section    .text.unlikely,"x"
LCOLDE1:
    .text
LHOTE1:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.unlikely,"x"
LCOLDB2:
    .section    .text.startup,"x"
LHOTB2:
    .p2align 4,,15
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB1084:
    .cfi_startproc
    leal    4(%esp), %ecx
    .cfi_def_cfa 1, 0
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    .cfi_escape 0x10,0x5,0x2,0x75,0
    movl    %esp, %ebp
    pushl   %edi
    pushl   %esi
    pushl   %ebx
    pushl   %ecx
    .cfi_escape 0xf,0x3,0x75,0x70,0x6
    .cfi_escape 0x10,0x7,0x2,0x75,0x7c
    .cfi_escape 0x10,0x6,0x2,0x75,0x78
    .cfi_escape 0x10,0x3,0x2,0x75,0x74
    xorl    %edi, %edi
    subl    $24, %esp
    call    ___main
L4:
    movl    %edi, (%esp)
    movl    $__ZSt4cout, %ecx
    call    __ZNSolsEi
    movl    %eax, %esi
    movl    (%eax), %eax
    subl    $4, %esp
    movl    -12(%eax), %eax
    movl    124(%esi,%eax), %ebx
    testl   %ebx, %ebx
    je  L15
    cmpb    $0, 28(%ebx)
    je  L5
    movsbl  39(%ebx), %eax
L6:
    movl    %esi, %ecx
    movl    %eax, (%esp)
    addl    $1000000000, %edi
    call    __ZNSo3putEc
    subl    $4, %esp
    movl    %eax, %ecx
    call    __ZNSo5flushEv
    jmp L4
    .p2align 4,,10
L5:
    movl    %ebx, %ecx
    call    __ZNKSt5ctypeIcE13_M_widen_initEv
    movl    (%ebx), %eax
    movl    24(%eax), %edx
    movl    $10, %eax
    cmpl    $__ZNKSt5ctypeIcE8do_widenEc, %edx
    je  L6
    movl    $10, (%esp)
    movl    %ebx, %ecx
    call    *%edx
    movsbl  %al, %eax
    pushl   %edx
    jmp L6
L15:
    call    __ZSt16__throw_bad_castv
    .cfi_endproc
LFE1084:
    .section    .text.unlikely,"x"
LCOLDE2:
    .section    .text.startup,"x"
LHOTE2:
    .section    .text.unlikely,"x"
LCOLDB3:
    .section    .text.startup,"x"
LHOTB3:
    .p2align 4,,15
    .def    __GLOBAL__sub_I_main;   .scl    3;  .type   32; .endef
__GLOBAL__sub_I_main:
LFB1092:
    .cfi_startproc
    subl    $28, %esp
    .cfi_def_cfa_offset 32
    movl    $__ZStL8__ioinit, %ecx
    call    __ZNSt8ios_base4InitC1Ev
    movl    $___tcf_0, (%esp)
    call    _atexit
    addl    $28, %esp
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
LFE1092:
    .section    .text.unlikely,"x"
LCOLDE3:
    .section    .text.startup,"x"
LHOTE3:
    .section    .ctors,"w"
    .align 4
    .long   __GLOBAL__sub_I_main
.lcomm __ZStL8__ioinit,1,1
    .ident  "GCC: (i686-posix-dwarf-rev1, Built by MinGW-W64 project) 4.9.0"
    .def    __ZNSt8ios_base4InitD1Ev;   .scl    2;  .type   32; .endef
    .def    __ZNSolsEi; .scl    2;  .type   32; .endef
    .def    __ZNSo3putEc;   .scl    2;  .type   32; .endef
    .def    __ZNSo5flushEv; .scl    2;  .type   32; .endef
    .def    __ZNKSt5ctypeIcE13_M_widen_initEv;  .scl    2;  .type   32; .endef
    .def    __ZSt16__throw_bad_castv;   .scl    2;  .type   32; .endef
    .def    __ZNSt8ios_base4InitC1Ev;   .scl    2;  .type   32; .endef
    .def    _atexit;    .scl    2;  .type   32; .endef
Run Code Online (Sandbox Code Playgroud)

我甚至几乎不能阅读装配,但即使我能看到这addl $1000000000, %edi条线.结果代码看起来更像

for(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000)
    std::cout << i << std::endl;
Run Code Online (Sandbox Code Playgroud)

@TC的评论:

我怀疑它是这样的:(1)因为i任何大于2的值的迭代都有未定义的行为 - >(2)我们可以假设i <= 2为了优化目的 - >(3)循环条件总是为真 - >(4) )它被优化成无限循环.

让我想到将OP代码的汇编代码与下面代码的汇编代码进行比较,没有未定义的行为.

#include <iostream>

int main()
{
    // changed the termination condition
    for (int i = 0; i < 3; ++i)
        std::cout << i*1000000000 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

事实上,正确的代码具有终止条件.

    ; ...snip...
L6:
    mov ecx, edi
    mov DWORD PTR [esp], eax
    add esi, 1000000000
    call    __ZNSo3putEc
    sub esp, 4
    mov ecx, eax
    call    __ZNSo5flushEv
    cmp esi, -1294967296 // here it is
    jne L7
    lea esp, [ebp-16]
    xor eax, eax
    pop ecx
    ; ...snip...
Run Code Online (Sandbox Code Playgroud)

天哪,这完全不明显!这不公平!我要求用火试验!

处理它,你写了错误的代码,你应该感觉很糟糕.承担后果.

......或者,正确使用更好的诊断和更好的调试工具 - 这就是它们的用途:

  • 启用所有警告

    • -Wall是gcc选项,可以启用所有有用的警告而不会出现误报.这是您应该始终使用的最低限度.
    • gcc有许多其他警告选项,但是,它们没有启用,-Wall因为它们可能会警告误报
    • 不幸的是,Visual C++在提供有用警告的能力方面落后了.至少IDE默认启用一些.
  • 使用调试标志进行调试

    • for integer overflow会-ftrapv在溢出时捕获程序,
    • 锵编译器非常适合这样的:-fcatch-undefined-behavior抓住了很多不确定的行为实例(注:"a lot of" != "all of them")

我有一个不是我写的程序的意大利面乱,需要明天发货!HELP !!!!!! 111oneone

使用gcc -fwrapv

此选项指示编译器假设加法,减法和乘法的带符号算术溢出使用二进制补码表示.

1 - 此规则不适用于"无符号整数溢出",如§3.9.1.4所述

声明无符号的无符号整数应遵守算术模2 n的定律,其中n是该特定整数大小的值表示中的位数.

例如,UINT_MAX + 1数学定义的结果- 由算术模2n的规则

  • @vsoftco:正在发生的是[强度降低](http://en.wikipedia.org/wiki/Strength_reduction),更具体地说,[归纳变量消除](http://en.wikipedia.org/维基/ Induction_variable).编译器通过发出代码来消除乘法,而代码在每次迭代时将"i"增加1e9(并相应地改变循环条件).这是一个完全有效的优化,在"似乎"规则下,因为这个程序无法观察它的表现是否良好.唉,它不是,优化"泄漏". (28认同)
  • 我怀疑它是这样的:(1)因为`大于2的任何值的i`每次迭代是未定义行为 - >(2)我们可以假设,'我<= 2`优化的目的 - >(3)循环条件总是正确的 - >(4)它被优化成无限循环. (26认同)
  • @JulieinAustin虽然它对你来说可能是愚蠢的,但它完全合法.从积极的方面来说,编译器会向您发出警告. (14认同)
  • @JohannesD确定了这个原因.但是,这是一个糟糕的优化,因为循环终止条件不涉及溢出.强度降低的使用是可以的 - 我不知道处理器中的乘数(4*100000000)与(100000000 + 100000000 + 100000000 + 100000000)不同,并且回归"它未定义" - 谁知道"是合理的.但是替换应该是一个表现良好的循环,它执行4次并产生未定义的结果,执行的次数超过4次"因为它未定义!" 是愚蠢的. (8认同)
  • 我仍然不明白这里发生了什么......为什么"我"本身受到影响?一般来说,未定义的行为没有这种奇怪的副作用,毕竟,`i*100000000`应该是一个rvalue (7认同)
  • @RussellBorogove它如何反对程序员的意图?程序员希望他们的代码尽可能快地运行,尽可能清晰并做正确的事情.优化处理第一和第二种情况,第三种情况是不可能的,因此它会警告你.我希望我的编译器能够使我的循环计算效率更高,而不必为了优化它而编写难看的源代码. (6认同)
  • @JulieinAustin你似乎在暗示下一个C++标准将"未定义的行为"分为两类:可接受的未指定行为和不可接受的未指定行为,并且还指出发出警告是不够的; 相反,编译器应限制其优化能力/污染其代码库,以便在不可接受的情况下为您提供不同类型的警告或停止编译.似乎有点不合理,不太可能通过投票.如果您将编译器设置为在某些警告上失败,那么它可能会更好吗? (5认同)
  • 只是对未来的暗示:如果您希望能够理解编译器的汇编器输出*总是*使用`-Os`选项.未优化的代码被不必要的东西混淆,为速度优化的代码被重复的代码混淆.两者都隐藏了人眼的结构.针对空间优化的代码提供了编译器理解的内容. (4认同)
  • @JulieinAustin:在许多情况下,这种优化可以加速代码.如果您希望编译器不破坏代码(通过假设不会发生有符号整数溢出)而触发未定义的行为,则可以在gcc中使用`-fwrapv`.但是,请注意它会取消许多优化,具体取决于未定义的有符号整数溢出,并且它不合适C.如果您不同意此未定义,请不要使用C或C++. (3认同)
  • @vsoftco当程序执行具有未定义行为的东西时,C++标准不会对*整个*程序执行构成*任何*要求.这允许积极的优化. (2认同)
  • 并非所有合法的东西都是好主意。GCC 的使用变得越来越危险,因为它需要严格合法的优化机会,这显然与程序员的意图(以及数十年的编译器传统)背道而驰。在基准测试中看起来不错显然比成为一个有用的编译器更重要。 (2认同)
  • 对于downvoter:答案“没有用”吗?它有事实错误吗?请告诉我这是怎么回事。任何批评都可以帮助我改进我的答案。 (2认同)

Sha*_*our 68

简短的回答,gcc特别记录了这个问题,我们可以看到gcc 4.8发布说明中的内容(强调我的前进):

GCC现在使用更积极的分析来使用语言标准强加的约束来导出循环迭代次数的上限.这可能导致不合规程序不再按预期工作,例如SPEC CPU 2006 464.h264ref和416.gamess.添加了一个新选项-fno-aggressive-loop-optimizations以禁用此积极分析.在一些具有已知常数迭代次数的循环中,已知未定义的行为在到达或在最后一次迭代期间在循环中发生,GCC将警告循环中的未定义行为,而不是导出迭代次数的下限上限为循环.可以使用-Wno-aggressive-loop-optimizations禁用警告.

事实上,如果我们使用-fno-aggressive-loop-optimizations无限循环行为应该停止,它在我测试的所有情况下都会停止.

通过查看草案C++标准部分表达式4段,知道有符号整数溢出是未定义的行为,这个答案很长,其中说:5

如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义.[注意:大多数现有的C++实现忽略整数溢出.除零处理,使用零除数形成余数,所有浮点异常因机器而异,通常可通过库函数调整. - 注意

我们知道该标准表明未定义的行为是不可预测的,因为该定义附带的说明如下:

[注意:当本国际标准忽略任何明确的行为定义或程序使用错误的构造或错误数据时,可能会出现未定义的行为.允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的特定行为,终止翻译或执行(发布时)一条诊断信息).许多错误的程序结构不会产生未定义的行为; 他们需要被诊断出来. - 尾注]

但是,gcc优化器可以做些什么来将其转变为无限循环?听起来很古怪.但幸运的是gcc,我们在警告中找到了解决它的线索:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^
Run Code Online (Sandbox Code Playgroud)

线索是Waggressive-loop-optimizations,这是什么意思?幸运的是,对于我们来说,这并不是第一次这种优化以这种方式破坏代码而且我们很幸运,因为John Regehr已经在GCC 4.8版Breaks Broken SPEC 2006基准测试中记录了一个案例,其中显示了以下代码:

int d[16];

int SATD (void)
{
  int satd = 0, dd, k;
  for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
  }
  return satd;
}
Run Code Online (Sandbox Code Playgroud)

文章说:

未定义的行为是在退出循环之前访问d [16].在C99中,创建指向一个位置超过数组末尾的元素的指针是合法的,但不能取消引用该指针.

后来说:

详细而言,这是正在发生的事情.在看到d [++ k]时,AC编译器被允许假设k的递增值在数组边界内,因为否则会发生未定义的行为.对于此处的代码,GCC可以推断出k在0..15的范围内.稍后,当GCC看到k <16时,它对自己说:"啊哈 - 那个表达式总是正确的,所以我们有一个无限循环."这里的情况,编译器使用明确定义的假设来推断a有用的数据流事实,

那么编译器在某些情况下必须做的是假设因为有符号整数溢出是未定义的行为然后i必须总是小于4,因此我们有一个无限循环.

他解释说这非常类似于臭名昭着的Linux内核空指针检查删除,在这里看到这段代码:

struct foo *s = ...;
int x = s->f;
if (!s) return ERROR;
Run Code Online (Sandbox Code Playgroud)

gcc推断,由于s在被deferenced s->f;,自提领一空指针是未定义的行为则s不能为空,因此优化走if (!s)下一行检查.

这里的教训是,现代优化器在利用未定义的行为方面非常积极,而且很可能只会变得更具侵略性.显然,只有几个例子,我们可以看到优化器对程序员做了一些看起来完全不合理的事情,但从优化器的角度回顾是有道理的.

  • 据我所知,这是编译器作者在做什么(我以前写的编译器,甚至是优化或两个),但也有是"有用的",即使他们是"不确定"的行为,这迈向更加积极主动的优化只是精神错乱.您在上面引用的构造是错误的,但优化错误检查是用户敌对的. (7认同)
  • 我认为这是一件好事,我想要更好,更快的代码.永远不应该使用UB. (3认同)

M.M*_*M.M 23

tl; dr代码生成一个整数 + 正整数 == 负整数的测试.通常,优化器不会对此进行优化,但std::endl在下一个使用的特定情况下,编译器会优化此测试.我还没弄清楚到底有什么特别之处endl.


从-O1和更高级别的汇编代码,很明显gcc将循环重构为:

i = 0;
do {
    cout << i << endl;
    i += NUMBER;
} 
while (i != NUMBER * 4)
Run Code Online (Sandbox Code Playgroud)

正确工作的最大值是715827882,floor(INT_MAX/3).汇编片段-O1是:

L4:
movsbl  %al, %eax
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
addl    $715827882, %esi
cmpl    $-1431655768, %esi
jne L6
    // fallthrough to "return" code
Run Code Online (Sandbox Code Playgroud)

注意,-14316557684 * 7158278822的补码.

点击-O2优化以下内容:

L4:
movsbl  %al, %eax
addl    $715827882, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655768, %esi
jne L6
leal    -8(%ebp), %esp
jne L6 
   // fallthrough to "return" code
Run Code Online (Sandbox Code Playgroud)

因此,所做的优化仅仅addl是向上移动.

如果我们重新编译,715827883那么除了更改的数字和测试值之外,-O1版本是相同的.然而,-O2然后做出改变:

L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2
Run Code Online (Sandbox Code Playgroud)

凡有cmpl $-1431655764, %esi-O1,该行已经为删除-O2.优化器必须已经决定添加715827883%esi永远不会相等-1431655764.

这非常令人费解.添加它INT_MIN+1 产生预期的结果,所以优化器必须已经决定%esi永远不会INT_MIN+1,我不知道为什么它会决定.

在工作示例中,似乎同样有效的结论是添加715827882到数字不能相等INT_MIN + 715827882 - 2!(这只有在实际发生环绕时才可能),但它不会优化该示例中的行.


我使用的代码是:

#include <iostream>
#include <cstdio>

int main()
{
    for (int i = 0; i < 4; ++i)
    {
        //volatile int j = i*715827883;
        volatile int j = i*715827882;
        printf("%d\n", j);

        std::endl(std::cout);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果std::endl(std::cout)删除,则不再进行优化.实际上,替换它std::cout.put('\n'); std::flush(std::cout);也会导致优化不会发生,即使std::endl内联也是如此.

内联std::endl似乎影响了循环结构的早期部分(我不太明白它在做什么,但我会在这里发布以防其他人这样做):

使用原始代码和-O2:

L2:
movl    %esi, 28(%esp)
movl    28(%esp), %eax
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    __ZSt4cout, %eax
movl    -12(%eax), %eax
movl    __ZSt4cout+124(%eax), %ebx
testl   %ebx, %ebx
je  L10
cmpb    $0, 28(%ebx)
je  L3
movzbl  39(%ebx), %eax
L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2                  // no test
Run Code Online (Sandbox Code Playgroud)

用mymanual内联std::endl,-O2:

L3:
movl    %ebx, 28(%esp)
movl    28(%esp), %eax
addl    $715827883, %ebx
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    $10, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    $__ZSt4cout, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655764, %ebx
jne L3
xorl    %eax, %eax
Run Code Online (Sandbox Code Playgroud)

这两者之间的区别在于%esi原始%ebx版本和第二版本中使用的区别; 在一般情况下%esi%ebx一般情况下定义的语义是否存在差异?(我对x86汇编不太了解).


小智 7

在gcc中报告此错误的另一个示例是当您有一个循环执行一定数量的迭代时,但是您使用计数器变量作为具有少于该项数的数组的索引,例如:

int a[50], x;

for( i=0; i < 1000; i++) x = a[i];
Run Code Online (Sandbox Code Playgroud)

编译器可以确定此循环将尝试访问数组'a'之外的内存.编译器用这个相当神秘的消息抱怨这个:

迭代xxu调用未定义的行为[-Werror = aggressive-loop-optimizations]


hac*_*cks 6

我不能得到的是为什么我的值被溢出操作打破了?

似乎整数溢出发生在第4次迭代(for i = 3)中. signed整数溢出调用未定义的行为.在这种情况下,无法预测任何事情.循环可能只迭代4次数,或者它可能会变为无限或其他任何东西!
结果可能会因编译器与编译器的不同而不同,甚至可能因同一编译器

C11:1.3.24未定义的行为:

本国际标准没有要求的
行为[注意:当本国际标准忽略任何明确的行为定义或程序使用错误的结构或错误数据时,可能会出现未定义的行为.允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的特定行为,终止翻译或执行(发布时)一条诊断信息).许多错误的程序结构不会产生未定义的行为; 他们需要被诊断出来. - 尾注]

  • "我认为这是不明确的行为,从这一点来看,我不关心它是如何或为什么会中断.标准允许它破裂.没有其他问题." 你可能没有这样的意思,但似乎就是这样.我希望在SO上看到这种(不幸的共同)态度.这实际上没用.如果你得到用户输入,那么在每个有符号整数运算*之后检查溢出*是不合理的,即使标准说*程序的任何其他*部分可能因此而爆炸.理解*它如何打破*确实*有助于在实践中避免这样的问题. (5认同)
  • 你是对的,解释我为什么倒卖是公平的.这个答案中的信息是正确的,但它没有教育性,它完全忽略了房间里的大象:破损显然发生在*不同的地方*(停止条件)比导致溢出的操作.虽然这是这个问题的核心,但没有解释在这种特定情况下如何破坏事物的机制.这是一个典型的坏教师情况,教师的答案不仅没有解决问题的核心,而且还阻碍了进一步的问题.它几乎听起来像...... (4认同)
  • @Szabolcs:最好将C视为两种语言,其中一种旨在使简单的编译器能够在程序员的帮助下,利用能够在其预期目标平台上可靠地构造的程序员来实现合理有效的可执行代码。其他语言,因此被标准委员会忽略,第二种语言排除了标准不要求其支持的所有此类构造,目的是允许编译器应用其他优化,这些优化可能会或可能不会超过程序员所必须的。放弃。 (2认同)