use*_*044 5 c linux gcc posix clang
现在,你们中的一些人会想要大喊未定义的行为,但是有一个问题.该类型int64_t不是由C标准定义,而是由POSIX定义.POSIX将此类型定义为:
带符号的整数类型,宽度为N,无填充位和二进制补码表示.
它不会留下这个实现来定义,并且绝对不允许将它视为无界整数.
linux$ cat x.c
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
int stupid (int64_t a) {
return (a+1) > a;
}
int main(void)
{
int v;
printf("%d\n", v = stupid(INT64_MAX));
exit(v);
}
linux$ gcc -ox x.c -Wall && ./x
0
linux$ gcc -ox x.c -Wall -O2 && ./x # THIS IS THE ERROR.
1
linux$ gcc --version
gcc (Debian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
linux$ uname -a
Linux localhost 3.14.13-0-amd64 #1 SMP Sat Jul 26 20:03:23 BST 2014 x86_64 GNU/Linux
linux$ getconf LONG_BIT
32
linux$
Run Code Online (Sandbox Code Playgroud)
显然,这里有一个问题......它是什么?
我错过了某种隐性演员吗?
我仍然会喊出不明确的行为.
这里的推理很简单,编译器假设您是一个完美的程序员,并且永远不会编写任何可能导致未定义行为的代码.
所以当它看到你的功能时:
int stupid (int64_t a) {
return (a+1) > a;
}
Run Code Online (Sandbox Code Playgroud)
它假定你永远不会打电话给它a==INT64_MAX,因为那将是UB.
因此,这个功能可以简单地优化为:
int stupid (int64_t a) {
return 1;
}
Run Code Online (Sandbox Code Playgroud)
然后可以适当地内联.
我建议你阅读每个C程序员应该知道的关于未定义行为的内容,以获得有关编译器如何利用UB进行优化的更多解释.
你不需要去POSIX对它进行排序,ISO C控制这个特定的方面(下面的参考是C11标准).
这个答案的其余部分将成为所有"语言律师",以显示为什么将未添加的行为添加到已签名的值中,以及为什么两个答案(真和假)都有效.
首先,您int64_t在ISO中未定义的争用并不十分正确.科7.20.1.1 Exact-width integer types州,当提到intN_t类型,即:
typedef名称指定有符号整数类型,其宽度为N,无填充位和二进制补码表示.因此,表示这样的带符号整数类型,其宽度恰好为8位.
intN_tint8_t这些类型是可选的.但是,如果实现提供宽度为8,16,32或64位的整数类型,没有填充位,并且(对于具有二进制补码表示的有符号类型),它应定义相应的typedef名称.
这就是为什么你不需要担心POSIX以某种方式定义这些类型的原因,因为ISO定义它们完全相同(两个补码,没有填充等),假设它具有适当的能力.
所以,现在我们已经建立了ISO 确实定义它们(如果是在实现中可用的话),现在让我们来看看6.5 Expressions /5:
如果在计算表达式期间发生异常情况(即,如果结果未在数学上定义或未在其类型的可表示值范围内),则行为未定义.
添加两个相同的整数类型肯定会给你相同的类型(至少在int64_t整数提升完成点的位置,远远高于整数提升1的点),因为这是由指定的通常算术转换决定的6.3.1.8.在处理各种浮点类型(其中int64_t没有)的部分之后,我们看到:
如果两个操作数具有相同的类型,则不需要进一步转换.
在同一部分的早期,您会找到一个声明,该声明在找到常见类型后指示结果的类型:
除非另有明确说明,否则共同实数类型也是结果的对应实数类型.
因此,假设结果INT64_MAX+1实际上不适合int64_t变量,则行为未定义.
根据您的注释,编码int64_t指示添加一个将包装,您必须理解,它不会更改它根本未定义的子句.在这种情况下,实现仍然可以自由地执行任何操作,即使根据您的想法没有意义.
并且,在任何情况下,表达式INT64_MAX + 1 > INT64_MAX(其中1经历整数提升为a int64_t)可以简单地编译为,1因为可以说比实际递增值和进行比较更快.这是正确的结果,因为任何东西都是正确的结果:-)
从这个意义上讲,它与实现转换没有什么不同:
int ub (int i) { return i++ * i++; } // big no-no
:
int x = ub (3);
Run Code Online (Sandbox Code Playgroud)
进入更简单,几乎肯定更快:
int x = 0;
Run Code Online (Sandbox Code Playgroud)
您可能认为答案会更好9或者12(取决于++执行副作用的时间)但是,如果未定义的行为是打破编码器和编译器之间的契约,编译器可以自由地做任何想做的事情.
在任何情况下,如果你想要一个定义良好的函数版本,你可以选择以下内容:
int stupid (int64_t a) {
return (a == INT64_MAX) ? 0 : 1;
}
Run Code Online (Sandbox Code Playgroud)
这可以在不诉诸未定义的行为的情况下获得所需/预期的结果:-)
1有可能在这里是边缘情况下,如果的宽度int实际上是大于64位.在这种情况下,整数促销很可能会强制转换int64_t为a int,从而可以很好地定义表达式.我没有详细研究过,所以可能是错的(换句话说,不要把它视为我答案中的福音部分)但是值得记住的是要检查一下是否有int更多的实现超过64位宽.
| 归档时间: |
|
| 查看次数: |
673 次 |
| 最近记录: |