数据类型与常量变量
掌握基本类型大小与范围、signed/unsigned 语义、const/volatile/restrict 与变量存储位置,理解 sizeof 与 limits.h
- 来源
- 补充
- 纠错
标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原
完成标准(本章)
- 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180 秒
- ✏️ 已练习:小练习正确率 ≥ 60%
- 📝 已通过测验:分数 ≥ 60 分
- 🛠️ 已掌握还需完成实践任务
1.2.2 数据类型与常量变量
本章来源:基本类型分类与大小、signed/unsigned、变量声明与初始化、作用域与生命周期来自《第一阶段讲义》13.x/14.x 节【来源】,经重新组织表述;三条【纠错】已落地:sizeof(void)=1 是 GCC 扩展(err-7.3-9)、浮点常量默认 double(err-7.3-10)、volatile≠多任务同步方案(err-7.1-30);全部示例与练习为新编【补充】并本机实测(gcc 15.2.0 / MinGW-w64 x86_64,约 10% 新编)。平台适用性 universal。
① 学习目标
- 说出 char/short/int/long/long long/float/double 的最小范围保证与本机实测大小;
- 用
sizeof与limits.h/float.h实测任意类型的字节数与边界值,不背"固定表"; - 说清 signed/unsigned 的语义差别,并知道有符号溢出是未定义行为(UB);
- 说出整数字面量与浮点字面量的默认类型(3.14 是 double、3.14f 才是 float);
- 区分 const / volatile / restrict 的语义与典型场景,并说出 volatile 的一个常见误区;
- 说清局部/全局/static 变量的作用域、生命周期与存储位置差异。
② 前置知识
- 必选:1.2.1(C 程序结构与标准演进——本章假设能独立编译运行单文件程序)。
③ 核心概念【来源】
| 概念 | 说明 |
|---|---|
| 基本整型 | char(恒 1 字节)、short、int、long、long long。标准只保证最小范围(int ≥16 位、long ≥32 位、long long ≥64 位)与"short ≤ int ≤ long ≤ long long"的相对关系;本机实测 1/2/4/4/8 字节 |
sizeof 运算符 | 编译期求类型/表达式的字节数;sizeof(char) 恒为 1;结果是平台相关的(本机 MinGW long=4,Linux x86_64 long=8) |
limits.h / float.h | 各类型边界值的标准来源(INT_MAX、UINT_MAX、FLT_MIN、DBL_MAX 等),跨平台代码一律查它而不是背数字 |
| signed / unsigned | 默认整型有符号;unsigned 范围 0~2ⁿ-1。unsigned 溢出是定义的(wrap,模 2ⁿ);有符号溢出是未定义行为(UB),不能当 wrap 规律 |
_Bool / bool | _Bool 只存 0/1(本机 1 字节);#include <stdbool.h> 后用 bool/true/false |
| 浮点类型 | float(约 6-7 位有效数字)/ double(约 15-16 位)/ long double;浮点比较用容差不用 == |
| 整数字面量默认类型 | 十进制/八进制/十六进制常量按 int → long → long long 第一个"放得下"的类型;后缀 u/U(unsigned)、l/L(long)、ll/LL(long long) |
| 浮点字面量默认类型 | **【纠错】**无后缀浮点常量默认是 double(3.14 是 double;3.14f 才是 float)——见 err-7.3-10 |
const | 编译期"只读"限定:修饰变量表示初始化后不可再赋值(编译器强制);函数参数 const T * 承诺不修改指向内容 |
volatile | 每次访问都强制从内存读写(编译器不得把访问优化掉/缓存进寄存器);用于硬件寄存器、信号/中断与主流程共享的变量;【纠错】不保证原子性与内存序,不是多线程同步方案——见 err-7.1-30 |
restrict | 指针限定符:向编译器承诺"该指针是所指向对象的唯一访问途径",允许更激进的优化(memcpy 的参数即用 restrict) |
| 作用域与生命周期 | 作用域=名字可见范围(块/文件);生命周期=对象存活期。局部变量(栈,auto):块内;全局变量:整个程序;函数内 static:生命周期=整个程序、作用域不变;文件内 static 函数/变量:只本文件可见 |
sizeof(void) | **【纠错】**GCC 扩展(结果 1),C 标准不允许对 void 类型求 sizeof——见 err-7.3-9 |
④ 通俗解释【补充】
- 类型是"箱子尺寸":
char是 1 格小箱、int是 4 格中箱。放得下的数相安无事,放不下就是溢出——unsigned 溢出是"转圈回到 0"(标准规定),signed 溢出是"箱子炸了,后果自负"(未定义行为); sizeof是尺子:别背尺寸表,编译期用尺子量一次,比任何"常见值"都可靠;const是"请勿改动"标签:贴了标签的箱子,编译器替你盯着,谁写谁报错;volatile是"别信缓存"标签:默认情况下编译器可能"上次看过就不再看";贴了 volatile 的箱子,每次都要亲自去仓库看最新值——但它管不了"两个人同时改"(那是锁/原子操作的活);- 函数内
static是"只进一次门的账本":第一次进来时记账本,之后每次进来翻同一本——账本(生命周期)活到程序结束,但只有这个函数(作用域)能翻它。
⑤ 示例代码
代码示例与验证记录
- examples/ex1-type-sizes.c基本类型 sizeof 与 limits.h/float.h 边界实测✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-15)编译:
gcc ex1-type-sizes.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
sizeof(char) = 1 sizeof(short) = 2 sizeof(int) = 4 sizeof(long) = 4 sizeof(long long) = 8 sizeof(float) = 4 sizeof(double) = 8 sizeof(long double) = 16 sizeof(void*) = 8 sizeof(_Bool) = 1 char 范围: -128 ~ 127 short 范围: -32768 ~ 32767 int 范围: -2147483648 ~ 2147483647 long long 范围: -9223372036854775808 ~ 9223372036854775807 unsigned int 最大值: 4294967295 float 范围: 1.17549e-38 ~ 3.40282e+38 double 范围: 2.22507e-308 ~ 1.79769e+308
差异说明:本输出为 gcc 15.2.0 / MinGW-w64 x86_64 实测(0 警告)。注意 long=4 字节(Windows LLP64 数据模型): Linux x86_64 为 8 字节(LP64)——sizeof 结果随平台变化,这就是"量而不背"的原因
完整源码见 /code 代码示例页
- examples/ex2-const-volatile.cconst 只读保护 / volatile 指针访问 / 函数内 static 计数✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-15)编译:
gcc ex2-const-volatile.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
N = 10 两次 volatile 访问后 reg = 2 第1次 counter() = 1 第2次 counter() = 2 第3次 counter() = 3
差异说明:本机实测(0 警告)。const 赋值错误已用注释演示(取消注释会编译失败:assignment of read-only variable); static 计数 1/2/3 证明值在调用间保留且只初始化一次
完整源码见 /code 代码示例页
- examples/ex3-sizeof-void-diag.csizeof(void) 的 GCC 扩展警告(diagnostic:预期警告)诊断示例(预期报错/警告)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-15)编译:
gcc ex3-sizeof-void-diag.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic(预期警告,exit 0)适用环境:LinuxWindows(MinGW)展开预期输出(实测)
ex3-sizeof-void-diag.c:4:28: warning: invalid application of 'sizeof' to a void type [-Wpointer-arith] 4 | printf("%zu\n", sizeof(void)); /* GCC 扩展;C 标准不允许对 void 求 sizeof */ | ^~~~ 运行输出:1差异说明:gcc 15.2.0 实测:-std=c11 -Wall -Wextra -Wpedantic 下给出上述警告(-Wpointer-arith)且仍能编译、结果为 1—— 这正是 err-7.3-9 说的"GCC 扩展";C 标准不允许对 void 求 sizeof,严格工程(-Werror)会编译失败。 language 用 text 使 verify:examples 不把本示例当作必须编译通过的示例
完整源码见 /code 代码示例页
⑥ 编译与运行方法
gcc ex1-type-sizes.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic
ex1.exe (Linux: ./ex1)- ex1 打印全部类型 sizeof 与 limits.h/float.h 边界,本机实测输出见示例卡(注意本机
long=4 字节:MinGW 的 LLP64 数据模型;Linux x86_64 为 8 字节); - ex2 演示 const 只读保护、volatile 的指针访问与函数内 static 计数(真实输出见示例卡);
- ex3 是 diagnostic 示例(
sizeof(void)的 GCC 扩展警告),预期出现警告,不作为普通运行示例。
⑦ 常见错误
| 症状 | 原因 | 解决 |
|---|---|---|
| 【纠错】看到资料写 sizeof(void)=1 | 那是 GCC 扩展,不是 C 标准 | 标准写法 sizeof(char)/sizeof(表达式);见 err-7.3-9(ex3 实测警告) |
| 【纠错】以为浮点常量"没有默认类型" | 无后缀浮点常量默认就是 double | 3.14 是 double,3.14f 才是 float;见 err-7.3-10 |
| 【纠错】用 volatile 做多线程共享变量的同步 | volatile 不保证原子性与内存序 | 多线程用互斥锁/原子操作;见 err-7.1-30 |
| int 溢出得到"负数的相反数",当成 wrap 规律 | 有符号溢出是 UB,编译器优化后表现可能"反常" | 需要 wrap 就用 unsigned;有符号运算避免溢出 |
用 %d 打印 long/unsigned/size_t 结果错乱 | 格式说明符与实参类型不匹配(UB) | long→%ld、unsigned→%u、size_t→%zu |
| 32 位机器上写死的 4 字节假设在 64 位/嵌入式上失效 | sizeof 与边界随平台变化 | 一律 sizeof/limits.h 实测;移植代码避免硬编码 |
⑧ 小练习
小练习
学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)ex-1-2-2-1.C 标准保证字节数恒为 1 的类型是?(单选)
◌ 未作答ex-1-2-2-2.int 变量自增越过 INT_MAX 后,结果是?(单选)
◌ 未作答ex-1-2-2-3.代码 `unsigned int u = 10; int i = -1;` 中 `i < u` 的结果是?(单选)
◌ 未作答
⑨ 章节测验
章节测验
⑩ 实战任务
实践任务
sizeof 全类型实测表 + volatile 寄存器访问小实验
参照 ex1 写出自己的"类型实测表"程序(打印全部类型 sizeof 与 int/unsigned/float/double 边界值), 并参照 ex2 写一个 volatile 变量经指针连续两次自增的小程序;把输出记录进笔记, 并能说出 volatile 的三个使用场景与一个误区。
输入与输出
无外部输入。交付物:① 自写的 sizeof 实测程序与完整输出记录;② volatile 指针访问程序(输出 2); ③ 笔记小结(volatile 三场景 + 一误区)。
功能要求
- 自写 sizeof 程序:打印 char/short/int/long/long long/float/double/long double/指针/_Bool 的 sizeof
- 用 limits.h 打印 INT_MAX/UINT_MAX、float.h 打印 FLT_MAX/DBL_MAX
- 自写 volatile 实验:volatile int 经 volatile 指针连续两次 +1,输出 2
- 笔记写出 volatile 三个场景(硬件寄存器/信号处理共享变量/setjmp-longjmp)与一个误区(多线程同步)
限制条件
- 编译命令 -std=c11 -Wall -Wextra -Wpedantic,正常示例 0 警告
- 不运行任何未定义行为代码
验收步骤(自检清单 0/3)
验收标准
- sizeof 程序覆盖全部要求类型且输出与 limits.h/float.h 一致(验收步骤 1)
- volatile 程序输出为 2 且 0 警告(验收步骤 2)
- 小结三场景与误区表述正确(误区必须提到"不保证原子性/内存序")(验收步骤 3)
常见失败原因
- sizeof 程序漏了 long 或指针(平台差异最明显的两个)
- 背字节数而不是看本机输出
- 把 volatile 写成多线程同步手段
- 有符号/无符号混用比较出现反直觉结果还不知道原因
可选扩展
- 对比 Linux x86_64 的 sizeof(long)(应为 8),理解 LP64 与 LLP64
- 用 gcc -O2 与 -O0 分别编译 volatile 实验对比反汇编(了解即可,GDB 章再深入)
完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)
⑪ 面试问题
面试问题
sizeof(int) 为什么不能背成固定 4 字节?高频C/C++ · easy
要点:sizeof 结果由实现/平台(数据模型、ABI)决定:标准只保证最小范围与相对关系;本机 MinGW 的 long 是 4 字节、Linux x86_64 是 8 字节,嵌入式可能不同。
C 标准对整型只规定最小范围(如 int 至少 16 位)与"short ≤ int ≤ long ≤ long long"的相对关系, 具体字节数由实现的数据模型决定:Windows/MinGW 用 LLP64(long=4、指针=8),Linux x86_64 用 LP64(long=8)。 所以跨平台代码一律用 sizeof 实测 + limits.h 边界值(INT_MAX 等),不硬编码、不背表。 唯一恒定的只有 sizeof(char)=1。
追问:- 追问:sizeof 是运行时算的吗?(不,编译期运算符)
评分要点:- 平台相关而非语言规定
- sizeof/limits.h 的用法
- char 恒 1
const 和 volatile 分别解决什么问题?可以同时修饰一个变量吗?高频C/C++ · medium
要点:const 表达"只读"(编译器强制不可再赋值);volatile 表达"每次访问都真实读写内存"(防优化)。可以同时用:const volatile 典型是"只读的硬件状态寄存器"。
const:语义是"初始化后不再修改",编译器可据此优化与静态检查赋值错误(const T * 承诺不修改指向对象)。 volatile:语义是"每次访问必须从内存读写",编译器不得把访问缓存/合并/删除——用于硬件寄存器、 信号/中断与主流程共享的变量。两者正交:const volatile 表示"这个对象外部会变(volatile), 但程序不许写它(const)",如只读状态寄存器。误区:volatile 不是多线程同步手段(不保证原子性/内存序)。
追问:- 追问:volatile 能防止多线程数据竞争吗?(不能,需要锁/原子操作)
评分要点:- const 语义与优化
- volatile 语义与场景
- const volatile 组合
- volatile 误区
int 和 unsigned int 一起比较,为什么结果常常"反直觉"?高频C/C++ · medium
要点:常规算术转换会把 int 提升为 unsigned int:-1 变成 4294967295,与 10 比较为假。
混合算术/比较时按"常规算术转换"把低等级转为高等级:有符号 int 与无符号 unsigned int 同宽时, 有符号操作数被转换为无符号——int 的 -1 变为 4294967295(模 2³²),于是 i < u 为假。 这是 C 语言设计的历史包袱,编写时要显式处理:比较前统一类型,或避免有符号/无符号混用; 编译器 -Wall -Wextra(-Wsign-compare)会给出警告。
追问:- 追问:怎么消除这类隐患?(统一类型/显式转换/用 size_t 与带符号循环变量时小心)
评分要点:- 常规算术转换规则
- 具体例子 -1 vs 10
- 规避方法
⑫ 延伸阅读
- 《C 程序设计语言》(K&R,第 2 章:类型、运算符与表达式——只引书名);
- C11 标准草案 N1570 §6.2.5(类型)、§6.7.3(限定符)——标准条文为权威口径;
- 本地手册:
man limits.h或查看编译器的limits.h头文件(路径用gcc -E -P展开查看); - 下一章预告:1.2.3 运算符与优先级——把类型"算"起来,并避开求值顺序陷阱。
迁移训练(migration training)
把本章技能迁移到 Linux(x86_64) 与 嵌入式(ARM 32 位):
| 项 | 本机(MinGW x86_64) | Linux x86_64 | 嵌入式 ARM 32 位 |
|---|---|---|---|
| sizeof(long) | 4(LLP64) | 8(LP64) | 4 |
| sizeof(void*) | 8 | 8 | 4 |
| sizeof(long double) | 16 | 16 | 8 或 16(实现不同) |
| char 字节数 | 1 | 1 | 1(恒为 1) |
| 边界值来源 | limits.h | limits.h | limits.h(交叉编译器自带) |
不变的:类型分类、signed/unsigned 语义、const/volatile/restrict 语义、sizeof(char)=1、limits.h 用法;要改的:具体字节数与边界值——所以"量"(sizeof)和"查"(limits.h),不"背"。
内容来源映射
| 内容部分 | 资料 | 位置 | 标记 | 说明 |
|---|---|---|---|---|
| 基本类型分类与大小范围、signed/unsigned、变量声明与初始化、作用域与生命周期 | 第一阶段讲义 | 13.x/14.x 节 | 【来源】 | 正文在原资料基础上重新组织表述,未大段复制原文 |
| sizeof(void) 为 1 的表述 | 第一阶段讲义 | 13.x 节(PAGE 189) | 【纠错】 | 见勘误 err-7.3-9:GCC 扩展,C 标准不允许对 void 求 sizeof |
| 浮点常量默认类型表述 | 第一阶段讲义 | 14.x 节(PAGE 204) | 【纠错】 | 见勘误 err-7.3-10:无后缀浮点常量默认 double |
| volatile 用于多任务共享变量的表述 | 讲义16 | PAGE 27 | 【纠错】 | 见勘误 err-7.1-30:volatile 不保证原子性与内存序 |
| 全部示例代码、sizeof/limits.h 实验、通俗解释(箱子/标签类比)、练习/测验/任务/面试题 | 无 | 【补充】 | 原资料示例多为截图无法还原;本章示例全部重写并本机实测(gcc 15.2.0 / MinGW-w64 x86_64,约 10% 新编) |
本章勘误与更新记录(【纠错】/【更新】)
err-7.3-9 · 表述修正 · 出处 PAGE 189
原文:资料 sizeof 表把 sizeof(void) 直接写为 1
正确:sizeof(void) 为 1 是 GCC 扩展:C 标准不允许对 void 类型求 sizeof(void 是"不完整类型"),标准写法用 sizeof(char)(恒为 1)或 sizeof(表达式)
原因:第一步报告 7.3 第 9 条:把 GCC 扩展当成语言规则会误导读者
err-7.3-10 · 表述修正 · 出处 PAGE 204
原文:资料对浮点常量"默认类型"表述自相矛盾(一处说无默认类型、一处说 double)
正确:无后缀浮点常量默认是 double:3.14 是 double,3.14f 才是 float;这是明确的语言规则,不存在"无默认类型"
原因:第一步报告 7.3 第 10 条:资料自相矛盾,需按标准统一表述
err-7.1-30 · 技术错误 · 出处 PAGE 27
原文:资料把 volatile 列为多任务共享变量的解决方案
正确:volatile 只保证每次访问都从内存读写(编译器不优化掉该访问),不保证原子性也不提供内存序;多任务共享变量需要互斥锁或原子操作(1.3.11 展开)
原因:第一步报告 7.1 第 30 条:volatile 用于多线程同步是常见严重误区