C 语言 1000 问|Flomo 原始总表迁移¶
本页保留原始导航与问题地图。建议后续按专题拆页,并从这里维护入口。
原始来源: https://v.flomoapp.com/mine/?memo_id=MjQ1NzM0NTk5
创建时间: 2026-07-08T07:10:45+08:00
最近更新: 2026-10-06T21:03:22+08:00
原文长度: 8134 字
▎ 专栏⑭·导航:C语言 1000 问全景——从语法到嵌入式实战
📎 专栏⑬硬件导航 → https://v.flomoapp.com/mine/?memo_id=MjQ3NTQ2MDE3
▎目录(10篇)
-
指针与数组的暗面——编译通过、运行错乱的三组概念
-
结构体对齐与填充——硬件寄存器映射时每个字节都会说话
-
预处理器——AUTOSAR 配置代码里的编译期引擎
-
嵌入式未定义行为实战——volatile 放错位置、位域跨编译器、整数溢出
-
const 与链接脚本——三种"以为在 ROM、实际在 RAM"的情况
-
static 作用域——嵌入式 C 程序员最顽固的坏习惯
-
指针与内存布局——堆栈、段、生命周期
-
嵌入式未定义行为深挖——UB 清单与防御
-
AUTOSAR 里的 C 实战——配置代码怎么读怎么写
-
C语言 15 项自检清单——上车前逐项打勾
▎补充篇(8篇)
-
补充一:字节序与可移植性——大端小端、int32_t、跨平台 → https://v.flomoapp.com/mine/?memo_id=MjUxMjA0MjM2
-
补充二:MISRA C——汽车代码的第一道红线 → https://v.flomoapp.com/mine/?memo_id=MjUxMjA0MjUx
-
补充三:并发与原子性——中断、多核共享变量的正确打开方式 → https://v.flomoapp.com/mine/?memo_id=MjUxMjA0Mjc0
-
补充四:函数指针与回调——AUTOSAR 配置表的底层机制 → https://v.flomoapp.com/mine/?memo_id=MjUxMjA0Mjky
-
补充五:内存管理——堆、栈、内存池 → https://v.flomoapp.com/mine/?memo_id=MjUyNDgxNDE5
-
补充六:C 与链接器的边界——启动代码、section、符号可见性 → https://v.flomoapp.com/mine/?memo_id=MjUyNDgxNTg5
-
补充七:头文件与模块化治理——include 卫生、编译单元边界、循环依赖 → https://v.flomoapp.com/mine/?memo_id=MjUyNDgxODMw
-
补充八:字符串与缓冲区安全——snprintf 一族与防御性编程 → https://v.flomoapp.com/mine/?memo_id=MjUyNDgxOTEy
▎1000 问全景(问题库)
▎ 基础语法与数据类型
-
声明(declaration)和定义(definition)的区别是什么?为什么这个区别在C里比在其他语言里更重要?
-
int在不同平台上可能是16位、32位或64位——写可移植代码时应该怎么处理整型长度问题?
-
unsigned和signed之间做算术运算时,C语言怎么隐式转换?什么情况下会出问题?
-
浮点数float和double在内存里的存储格式(IEEE 754)是什么样的?判断两个浮点数是否相等为什么不能直接写==?
▎ 存储类与作用域
-
auto、register、static、extern这四种存储类分别控制变量的什么属性?
-
static修饰局部变量、全局变量和函数时,行为各是什么?为什么static在C里被"重载"了?
-
const修饰变量和const修饰指针(const int *p vs int *const p)——编译器各保证了什么?
-
volatile是干什么的?它在嵌入式开发里最典型的场景有哪些?去掉volatile编译器会优化掉什么?
▎ 指针
-
指针到底是什么?它存的地址和这个地址上的内容为什么是两回事?
-
指针加减整数(p+1)时,地址到底加了几个字节?这个机制跟指针类型有什么关系?
-
NULL指针、野指针、悬空指针(dangling pointer)——三种指针各是怎么产生的?怎么避免?
-
函数指针是什么?把它作为回调函数使用时,编译器帮你做了什么?
-
二级指针(int **p)在什么场景下必须用?只用一级指针代替会出什么问题?
▎ 数组与字符串
-
数组名和指针是一回事吗?为什么sizeof(arr)和sizeof(ptr)结果不一样?
-
C语言为什么没有内置的字符串类型?以'\0'结尾的字符串设计带来了哪些问题?
-
strcpy、strcat、sprintf为什么被称为"不安全"的函数?它们的安全替代版本是怎么解决长度问题的?
-
二维数组在内存里是怎么排列的?为什么访问arr[i][j]时,维度的顺序对性能有影响?
▎ 结构体与联合体
-
结构体内存对齐(alignment)是怎么回事?为什么编译器会在成员之间插入填充字节?
-
位域(bit field)在什么场景下有用?用位域和手动按位操作各有什么优缺点?
-
联合体(union)和结构体(struct)在内存布局上有什么本质区别?联合体最常见的用途是什么?
-
结构体作为函数参数传值(pass by value)和传指针(pass by pointer)在性能上差在哪?
▎ 内存管理
-
堆(heap)和栈(stack)在C语言运行时里是怎么管理的?谁分配谁释放?
-
malloc的底层实现大概是怎么工作的?为什么malloc不一定每次调用都会去问操作系统要内存?
-
malloc之后必须free——"内存泄漏"具体是什么样的?一个程序泄漏了会怎么样?
-
什么是"内存碎片"?为什么长时间运行的嵌入式系统要尽量避免动态内存分配?
▎ 位操作
-
移位运算(<<和>>)在符号数和无符号数上的行为一样吗?右移有符号数是算术移位还是逻辑移位?
-
用位操作实现"取一个整数二进制表示中的第n位"——表达式怎么写?它和掩码(mask)的关系是什么?
-
嵌入式驱动中常见的"读-改-写"模式(如设置某个寄存器的某几位)——用位操作怎么实现才算安全?
▎ 预处理器
-
define定义的宏和真正的函数有什么本质区别?宏的"副作用问题"(如MAX(a++, b))是怎么产生的?
-
头文件里的 Include Guard 是干什么的?如果不加,编译器会遇到什么问题?
-
pragma once和Include Guard比有什么优缺点?为什么有些项目坚持用Include Guard?
▎ 文件I/O
-
fopen的"文本模式"和"二进制模式"在Windows和Linux上的行为有什么不同?这跟换行符'\n'的表示有什么关系?
-
fgets和gets有什么区别?为什么gets已经从C11标准里被移除了?
-
fread和fwrite的"块读写"设计——一次读4个256字节的块和一次读1个1024字节的块,性能一样吗?
-
文件指针(FILE *)和文件描述符(fd)——C标准库的IO函数和系统调用read/write之间隔了几层?
▎ 可变参数
-
printf是怎么知道传入了多少个参数的?va_list、va_start、va_arg、va_end的机制是什么?
-
自己写一个可变参数函数时,为什么必须有一个固定参数来确定可变参数的起始位置?
-
可变参数函数为什么是"类型不安全"的?printf传错参数类型时,那个崩溃是怎么发生的?
▎ 函数与递归
-
函数调用时,栈帧(stack frame)里存了哪些内容?返回地址和局部变量的布局是怎么样的?
-
递归函数为什么不遇到终止条件就会栈溢出?递归和非递归(迭代)之间,除了写法还有什么本质区别?
-
尾递归优化(tail call optimization)是什么?它解决了递归的什么问题?编译器怎么知道可以做这个优化?
▎ 多文件组织与模块化
-
头文件应该放声明还是定义?为什么在头文件里定义全局变量会有"多重定义"问题?
-
"前向声明"和"环形依赖"——两个头文件互相包含时为什么会编译失败?怎么解决?
-
static函数在模块化编程里起什么作用?它和普通函数在链接时有什么不同?
▎ 未定义行为与常见陷阱
-
什么是C语言的"未定义行为"(undefined behavior)?为什么C有这么多UB而其他语言没有?
-
缓冲区溢出(buffer overflow)是怎么发生的?一个简单的gets()如何能让攻击者改写返回地址?
-
整数溢出(overflow)在符号数和无符号数上的行为分别是定义的还是未定义的?
-
序列点(sequence point)是什么?为什么i=i++是未定义行为?
-
看一段代码判断它有没有未定义行为——为什么这种能力区分了"会写C"和"真正懂C"?
▎ 标准库深入(stdlib.h / string.h / math.h)
-
qsort排序需要一个比较函数指针——为什么标准库不直接内置一个默认的升序比较器?void *参数在这里解决了什么问题?
-
bsearch(二分查找)要求数组是"已排序的"——如果传入一个未排序的数组,bsearch会怎么样?它能检测出来吗?
-
atoi把一个字符串转成整数,如果传入"abc"会返回什么?atoi、strtol、sscanf三者在错误处理上有什么不同?
▎ 编译与链接
-
从.c文件到可执行文件,经历了哪几个阶段?每个阶段产出了什么?
-
静态链接和动态链接的本质区别是什么?嵌入式系统里为什么通常用静态链接?
-
"符号解析"和"重定位"在链接器里各做了什么?链接错误中的"undefined reference"和"multiple definition"分别发生在哪个阶段?
-
为什么在头文件里定义全局变量并让多个.c文件包含,链接时会报多重定义?但如果加上static或inline就不会?
▎ 高级特性
-
restrict关键字是干什么的?它对编译器优化有什么帮助?不加restrict的话编译器不敢做什么假设?
-
柔性数组成员(flexible array member)——struct的最后一个成员写成char data[]而不是char *data,两者在内存布局和分配方式上有什么不同?
-
C11引入的_Generic泛型选择——它是怎么让一个宏根据参数类型展开不同代码的?跟C++的重载比,它缺了什么?
▎ 防御性编程与安全
-
assert和static_assert的区别是什么?为什么说static_assert比assert更适合在嵌入式里做编译时检查?
-
MISRA C的核心规则可以归结为哪几条原则?它为什么要禁止动态内存分配(malloc/free)?
-
什么是"永不失败的函数"和"可能失败的函数"的接口设计约定?在C里怎么用返回值表达错误?
-
用errno报告错误和使用自定义错误枚举各有什么优缺点?AUTOSAR的Std_ReturnType走的是哪种风格?
▎ 非局部跳转(setjmp / longjmp)
-
setjmp/longjmp是干什么的?它和goto有什么本质区别?为什么说它是C里的"异常处理"?
-
longjmp跳回setjmp的位置后,栈上的局部变量会恢复成原来的值还是当前的值?这跟volatile有什么关系?
-
在嵌入式里用setjmp/longjmp做错误恢复时有什么风险?什么场景下它是合理的?
▎ 信号处理(signal.h)
-
信号(signal)在C语言层面和在操作系统层面分别是什么?SIGSEGV(段错误)是怎么产生的?
-
信号处理函数里能做什么、不能做什么?为什么说在信号处理函数里调用printf是危险的?
-
什么是"异步信号安全"(async-signal-safe)函数?在嵌入式里,一个ISR和一个信号处理函数,谁的限制更多?
▎ C标准演变
-
C89、C99、C11、C17、C23——每个版本最重要的一条改变是什么?为什么C99被认为是C的"爆发版本"?
-
C11引入的_Noreturn函数说明符是干什么的?它告诉编译器和程序员什么信息?
-
bool类型从C99的_Bool到C23可以直接用bool——这个演变过程反映了C标准委员会什么样的设计哲学?
▎ 内联函数与VLA
-
inline关键字在C和C++里的行为有什么不同?C里加了inline的函数为什么还要在某个.c文件里显式实例化一次?
-
变长数组(VLA)是C99引入的——它的长度在编译时未知,那它在栈上怎么分配?为什么C11把VLA改成了"可选特性"?
▎ C与C++互操作
-
extern "C"是干什么的?为什么C和C++链接同一个.o文件时会出现undefined reference?
-
一个在C里合法的函数指针赋值(如void *p = malloc)在C++里为什么需要强制转换?这反映了C和C++在类型系统上的什么差异?
▎ 对齐深入
-
alignas和alignof是C11引入的——在没有它们之前,嵌入式开发者怎么手动对齐缓冲区到4字节或32字节边界?
-
缓存行对齐(cache line alignment,通常是64字节)为什么在多核或DMA场景下重要?不对齐会发生什么?
▎ 多线程与原子操作(C11)
-
C11引入的thrd_create和pthread_create在接口设计上有什么不同?为什么C标准没有直接包装pthread?
-
什么是"数据竞争"(data race)?两个线程同时读一个变量的值,是数据竞争吗?
-
atomic_int和用互斥量保护的int,在性能和行为上有什么本质区别?
-
内存顺序(memory_order_relaxed、acquire、release、seq_cst)——这四个级别的强度各在什么场景下用?
▎ 时间处理(time.h)
-
time_t到底是个什么类型?为什么它通常是32位或64位的整数?2038年问题是怎么产生的?
-
clock_gettime和time函数有什么区别?为什么测量代码性能时应该用前者而不是后者?
-
嵌入式系统里经常需要的"定时器回调"——用POSIX timer_create和直接用硬件定时器中断有什么区别?
▎ 复合字面量与泛型深入
-
复合字面量(compound literal,如(struct point){.x=1, .y=2})是在栈上还是静态区?它的生命周期是多久?
-
_Generic既然可以在编译期做类型分发,它能模拟"函数重载"吗?如果能,跟C++的重载比差在哪?
▎ 宽字符与本地化
-
wchar_t在不同平台上的大小不一样(Linux上是4字节,Windows上是2字节)——这个不一致带来的最大麻烦是什么?
-
char字符串和wchar_t字符串之间怎么转换?mbstowcs和wcstombs在底层发生了什么?
▎ 环境交互与工具
-
getenv获取环境变量有什么安全风险?谁能修改环境变量?setenv和putenv的实现差异是什么?
-
atexit注册的退出函数是在什么时候调用的?如果多个atexit函数,调用顺序是先进先出还是后进先出?
-
内存调试工具(valgrind、AddressSanitizer)是怎么检测到"内存泄漏"和"越界访问"的?它们各自对运行性能有多大影响?
▎ 常见库函数遗漏
-
memmove和memcpy的区别是什么?为什么一个能处理重叠内存而另一个不能?
-
snprintf的返回值含义——如果缓冲区太小导致字符串被截断,它返回的是"实际写了多少"还是"本来要写多少"?
-
strtok为什么是"不可重入"的函数?strtok_r是怎么解决这个问题的?
▎ 编译实战(Makefile / 编译选项)
-
编译C程序时-O0、-O1、-O2、-O3、-Os这几个优化级别分别做了什么?在嵌入式调试时为什么通常先用-O0?
-
-Wall -Wextra -Werror这三个编译选项各自有什么作用?为什么项目里常用-Werror把警告变成错误?
-
Makefile里$@、$^、$<分别代表什么?一个最小可用的Makefile怎么写——只编译一个main.c和一个utils.c?
▎ 调试实战(GDB)
-
GDB里break、watch、catch三种断点的区别是什么?watch在什么场景下特别有用?
-
程序core dump了——怎么用GDB的bt(backtrace)和frame命令定位崩溃位置?先看什么再看什么?
-
GDB的print和display命令有什么不同?在for循环里想每次迭代都看某个变量的值,该用哪个?
▎ 安全编码实战
-
sprintf和snprintf的返回值有什么区别?用snprintf是否就绝对安全了——如果传入的size参数比实际缓冲区大呢?
-
整数溢出不仅会导致计算结果错误,在分配内存时还会导致缓冲区溢出——malloc(n * sizeof(type))在n很大时为什么有风险?怎么防?
-
C的字符串函数(strcpy、strcat、sprintf)的"不安全"本质是什么?用strncpy替代strcpy就安全了吗——strncpy不自动加'\0'的坑在哪?
▎ 性能分析实战
-
怎么判断你的C程序是CPU密集型的还是内存密集型的?perf top和gprof分别适合看什么?
-
内联函数和宏在性能优化上有什么区别?为什么编译器可能忽略你的inline关键字?
▎ 嵌入式C实战
-
嵌入式里常见的"寄存器位操作"模式——定义某个寄存器的某几位为A、某几位为B——C语言中怎么用宏封装一个"读-改-写"的通用模式?
-
链接脚本(.ld文件)里的.text、.data、.bss段分别放什么?为什么.bss段在可执行文件里不占空间但在RAM里要占?
▎ 跨平台C开发
-
代码要同时跑在x86 Linux和ARM Cortex-M上,int的大小不一样——怎么用C标准类型(int32_t、uint16_t)来保证跨平台一致?
-
大端和小端在不同的CPU上,通过CAN总线收了一个32位整数——怎么在C里安全地做字节序转换?
专栏⑭进度:导航 + 10/10篇 + 补充一~八(共 18 篇)✅ 全部完成
Area/技术/C语言¶
Wiki 维护说明¶
- Flomo 原笔记保留不动;本页为副本。
- 将已回答的问题链接到对应答案页,避免重复复制答案。
- 答案应记录平台/版本、示例、验证步骤与来源。