【C语言·004】整型常量的进制表示法与类型后缀规则

【C语言·004】整型常量的进制表明法与类型后缀规则

写 C 的人,都见过 1、077、0xFF 这类“看起来像数字”的东西。它们只是“字面量”——编译期的常量,但背后有一套严格的记法规则类型推断机制。理解这些细节,能帮你躲过许多肉眼难识的坑,列如“前导 0 变八进制”“位运算越界”“printf 打印错位”等。


一、整型常量的“长相”:四种进制前缀

十进制:123(无前缀)
八进制:077(前缀 0)
十六进制:0xFF 或 0XFF(前缀 0x/0X)
二进制:0b1010 或 0B1010(C23 标准引入;GNU/Clang 早有扩展)

  • 前导 0 陷阱:08 在标准 C 中是非法的(由于八进制没有 8),许多线上事故就栽在“填充位数时加了个 0”。
  • 数字分隔符(C23):可用单引号分隔数字提高可读性:1'000'000、0xFF'FF、0b1010'1100。编译器未启用 C23 时不要用。
  • 习惯上,位掩码更推荐用十六进制或二进制表明:0x80000000u、0b1u << 31,语义一眼能懂。

二、类型后缀:U / L / LL(可以组合)

  • u 或 U:无符号(unsigned)
  • l 或 L:long
  • ll 或 LL:long long(C99 引入)

组合写法(顺序不拘,推荐大写更清晰):

  • 123U、123L、123UL、123LU、123LL、123ULL、123LLU……
  • 不要用小写 l:1l 容易和 11 混淆,统一用 L/LL。

后缀不是“装饰品”,它直接决定常量的类型,从而影响整数提升重载决议(在 C++)位移/比较/打印等行为。


三、编译器如何推断整型常量的类型

C 有两套推断路径,取能容纳该值的第一个类型。

1)十进制常量(有符号优先)

int → long int → long long int

也就是不思考无符号,除非你显式写了 U。

2)八/十六/二进制常量(有符号与无符号交替)

int → unsigned int → long int → unsigned long int → long long int → unsigned long long int

这解释了为什么 0x80000000 往往是 unsigned int,而 2147483648(十进制)则会走到 long/long long。

不同平台的数据模型影响结论

  • LP64(大多数 64 位 Linux/Unix/macOS):int32 / long64 / long long64
  • LLP64(Windows 64 位):int32 / long32 / long long64

同一个字面量,在不同数据模型上得到的具体类型可能不同,但规则不变:能装下就选最“短”的那个(遵循上面的顺序)。


四、常见坑 & 典型案例

1)前导 0 变八进制

int a = 010;   // 8
int b = 8;     // 8
// a == b,但 010 的语义完全不同。写固定宽度时不要用前导 0。

2)位移越界与符号扩展

// 想要得到最高位的掩码
unsigned m1 = 1u << 31;   // 正确:结果是 0x80000000(在 32 位 unsigned 上)
int      m2 = 1  << 31;   // 未定义/实现相关:有符号位移到符号位,行为不可靠

结论:凡是位运算的“高位”构造,先加 u 再位移。

3)十六进制的“无符号”倾向

int          a = 2147483648;   // 十进制,类型需能容纳:一般是 long/long long
unsigned int b = 0x80000000;   // 十六进制可优先匹配 unsigned int

这俩常量长得像,但默认类型不同,参与运算时的整数提升也会不同。

4)printf 与后缀/类型不匹配

printf("%d
",  0x80000000);   // 在许多平台是未定义行为
printf("%u
",  0x80000000u);  // 正确
printf("%ld
", 2147483648L);  // long 用 %ld
printf("%lld
", 1LL << 60);   // long long 用 %lld

口诀:int→%d、unsigned→%u、long→%ld/%lu、long long→%lld/%llu。
别偷懒:先加后缀,再配格式

5)比较陷阱:有符号 vs 无符号

int      x = -1;
unsigned y = 1U;
printf("%d
", x < y);   // 0?1?——实现会把 x 转成 unsigned 比较,结果是 0

要点:只要表达式里有无符号类型参与,提升/转换就会发生。对边界值的比较,统一后缀与类型,别混用。


五、最佳实践清单(拿去即用)

  1. 位相关常量一律写无符号:1u << 31、0x80000000u。
  2. 边界/容量常量明确后缀:SIZE_MAX、UINT32_MAX,自写用 U/UL/ULL。
  3. 需要固定宽度?用 <stdint.h> 的宏:
  4. INT32_C(100)、UINT64_C(1) << 40(这些宏会自动加合适的后缀,最可移植)。
  5. 表达位掩码优先用十六进制或二进制,并分组:0xFF00'FF00u(C23)或 0b1111'0000u。
  6. 禁止前导 0 表明十进制。需要填充位数交给格式化/字符串,不要写在字面量上。
  7. printf/scanf 的格式串和类型成对出现;宁可啰嗦,不要赌编译器。
  8. 审阅代码时看到小写 l 后缀,一律改成 L/LL。
  9. 跨平台库里,面向接口写字面量:用宏(UINT32_C 等)或 static_assert 校验 sizeof 假设。

六、速查:不同进制 + 后缀示例

// 十进制(有符号优先)
123              // int
2147483648L      // long
9223372036854775807LL  // long long

// 八/十六/二进制(交替匹配无符号)
077u             // unsigned int(八进制)
0x80000000u      // unsigned int(十六进制)
0b1010'1100U     // C23 二进制 + 分隔符 + 无符号

// 固定宽度(最推荐)
uint32_t mask = UINT32_C(0x80000000);
int64_t  big  = INT64_C(9'223'372'036'854'775'807); // C23 分隔符

七、三道“读代码辨类型”小练习(含答案)

Q1

printf("%zu
", sizeof(0xFFFFFFFF));

它的类型是什么?为什么 sizeof 的结果在不同平台可能不同?

Q2

uint32_t m = 1 << 31;

这行有风险吗?应该怎么改?

Q3

long long a = 0x7FFFFFFF;
long long b = 2147483648;
printf("%lld %lld
", a, b);

a 与 b 的字面量类型分别是什么?赋值有没有隐式转换?

参考答案

  • A1:0xFFFFFFFF 走“八/十六/二进制路径”,会在 unsigned int/unsigned long/… 中择一。LP64 上可能是 unsigned int(4 字节),LLP64 上也可能是 unsigned long(4 字节),甚至 unsigned long long(8 字节),因此 sizeof 结果非固定
  • A2:有风险。1 为 int,1 << 31 可能触及符号位导致未定义/实现相关。应写 1u << 31 或 UINT32_C(1) << 31。
  • A3:0x7FFFFFFF 按规则一般为 int(能装下即选),再提升赋给 long long;2147483648(十进制)需要更大类型,一般直接是 long 或 long long。两者赋值都发生了隐式转换,但前者是从 int 转,后者可能无需再扩大。

结语

整型常量不是“随意写个数字”那么简单:进制前缀决定它的“长相”,后缀决定它的“身份”,而推断规则决定它在不同平台的落点。把这三件事讲清楚、写规范,你的位运算、边界判断、IO 打印和跨平台行为都会更稳。下一次敲下 0x80000000u 或 INT64_C(1) << 40,你就已经避开了半打常见 bug。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
梦梦贝莉雅的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容