类型后缀
给变量赋值的时候分两个阶段。
第一步,如果没有指定字面量的类型,C99会找到一个能容纳字面量值的最小的带符号类型(int / long int / long long int)。
第二步,转换成左边变量类型并赋值。
long i = 0xffff;
long j = 0xffffU;
long k = 0xffffUL;
在下面几种场景里,我们需要显式指定字面量的类型来避免问题。
printf("%lld", 1LL);
printf("%lld", 1);
long x = 10000000L * 4096L;
unsigned long long y = 1ULL << 36;
如前所述,赋值操作在后,数值计算在前,因此只把等号左边的变量声明成较长的数据类型是不足矣防止计算溢出的。
int x = -1;
printf("%d\n", x < 12);
int x = -1;
printf("%d\n", x < 12U);
编译过程中编译器会给出大量的告警提示信息来帮助我们避免一些未定义的行为和后期难以发现的 bug。
我们要对这些行为做到心中有数,因为有些潜藏的错误编译器无法识别出来。我们来看下面这个错误,预期高32bits全是0,实际上生成的代码做了符号扩展,最终结果高32bits全为1,这明显和我们的意图是不一致的。
关于移位操作,可以参考下 Linux 是如何用相关的宏来防止类似的问题的。
...
#ifdef __ASSEMBLY__
#define _AC(X,Y) X
#define _AT(T,X) X
#else
#define __AC(X,Y) (X##Y)
#define _AC(X,Y) __AC(X,Y)
#define _AT(T,X) ((T)(X))
#endif
#define _UL(x) (_AC(x, UL))
#define _ULL(x) (_AC(x, ULL))
#define _BITUL(x) (_UL(1) << (x))
#define _BITULL(x) (_ULL(1) << (x))
...
#include <uapi/linux/const.h>
#define UL(x) (_UL(x))
#define ULL(x) (_ULL(x))
...
#define BIT(nr) (UL(1) << (nr))
读者可能会问,为什么不直接在数值上带上后缀而要引入这些宏呢?原因是汇编代码不识别后缀,这些宏是为了能让相关的代码能够在C代码和汇编代码中都使用而引入的。