先问你一道送分题char s[] hello;那么sizeof(s)和strlen(s)分别是多少答案是6和5。可别小看这一题据这些年我面试新人、帮网友改代码的经验能在五分钟内把这两个东西讲清楚的人连三分之一都不到。很多人字符串处理写得飞起一问sizeof到底算的是哪块内存、strlen为什么需要头文件、数组名传进函数后为什么就“变了”立刻卡壳。这篇文章我就把sizeof和strlen的底层原理、使用场景、笔试高频陷阱一次性讲透再带你把网上流传的经典真题和易错选项逐题拆解一遍。内容覆盖数组、指针、字符串字面量、结构体对齐、动态内存申请这些 C 语言基础核心知识点。适合三类人看刚学完 C 语法想补基础入门知识的同学准备校招笔试、需要刷sizeof和strlen相关习题的应届生还有工作几年但遇到类似题目依然凭感觉猜答案的在职工程师。建议你找个能敲代码的环境边看边跑一遍印象会深得多。1. 先分清身份一个是运算符一个是库函数1.1 sizeof 是关键字不是函数也不需要头文件很多初学 C 的朋友被网上的写法误导以为sizeof是库函数还要问“sizeof 函数需要头文件吗”。这里我明确告诉你sizeof在 C 语言里是关键字是运算符不是函数。它长得像函数是因为我们习惯写成sizeof(类型)或sizeof(变量)括号看起来像函数调用。但 C 标准规定sizeof是单目运算符优先级和、-这类符号相同。对普通变量操作时括号甚至可以不写比如int x 10; printf(%zu\n, sizeof x); // 不写括号也能编译我在真实项目代码里见过有人写sizeof x语法上合法但我个人不建议你在工程里这样写可读性太差。笔试如果碰到这种写法你要能看懂它和sizeof(x)等价。既然是关键字它就不需要任何头文件。这点一定要记牢sizeof是编译器认识的符号不是运行时从某个库里调出来的函数。sizeof的核心运作机制是编译期求值。编译器在编译阶段就能算出结果生成的可执行代码里sizeof(x)已经被替换成了一个整数常量。所以它不消耗运行时间也不执行括号里的表达式。下一章我会用sizeof(i)这个经典例子给你看它到底有多“懒”。1.2 strlen 是标准库函数必须包含 string.hstrlen就完全是另一回事了。它是 C 标准库里的函数原型长这样size_t strlen(const char *s);使用它之前必须包含头文件#include string.hstrlen的工作原理可以理解为从你传给它的指针地址开始一个字节一个字节地往下读直到遇到第一个\0为止然后把走过的字节个数返回给你。它属于 C 语言字符串函数家族跟strcpy、strcmp、strcat这些是同一批成员都在string.h里。这里要引出第一个重要区分sizeof问的是“这块内存占了多大地方”strlen问的是“这个字符串到结束符为止有多长”。一个是地盘的大小一个是内容的长度。1.3 一张对照表说清本质区别把两者最关键的区别拉成一张表建议你直接存下来对比项sizeofstrlen本质运算符 / 关键字C 标准库函数头文件不需要需要string.h求值时机编译期运行期参数类型名或变量名以\0结尾的字符串指针返回值对象或类型占用的字节数到第一个\0之前的字符个数是否包含\0如果是字符数组包含不包含能否作用于未分配初始化的内存可以只算类型大小不能会越界读到不确定的字节对数组名的处理数组名在 sizeof 中保留“整个数组”信息数组名作为参数时退化为指针这张表只看可能还有点抽象后面每一章我都配了具体代码示例把表的每一行对应到真实场景里去理解。用一个生活类比帮助记忆sizeof相当于看房产证上的建筑面积厕所、阳台、墙体全给你算进去strlen相当于拿着卷尺从大门口往里量走到第一堵墙\0就停下只算这段可见路径的长度。所以同一个字符数组hellosizeof报的是 6 平米的完整结构strlen量出来的是 5 米的可见路长剩下 1 平米是藏在墙后的终止符。2. 字符串与数组最容易踩的 sizeof 坑2.1 数组名和指针变量sizeof 结果差远了这道题是笔试绝对的高频题char s[] hello; char *p hello; printf(%zu %zu\n, sizeof(s), sizeof(p)); printf(%zu %zu\n, strlen(s), strlen(p));在 64 位环境下输出结果是6 8 5 5如果你答错成6 5说明你把p的sizeof结果当成了字符串长度。这就是最大的坑s是数组p是指针变量两者类型完全不同。对于数组ssizeof(s)取的是整块数组内存的大小。hello在 C 语言里是一个char[6]类型除了h、e、l、l、o五个字母外末尾还自动补了一个\0所以整块数组占 6 个字节。这里的6是“整个容器的体积”不是“容器里有效内容的长度”。对于指针p它只是一个保存地址的变量。64 位系统上地址宽度是 8 字节所以sizeof(p)是 8。指针本身不关心你指向的内容是谁、有多长它只负责存一个地址。你把p指向hello也好指向hello world也好sizeof(p)都是 8因为指针变量所占的字节数与它指向什么东西毫无关系。strlen就不同了它接收参数时只看指针指向的内容从p指向的地址开始找\0找到o后面的终止符返回 5。所以strlen(p)结果和strlen(s)一样都是 5。记住这一点sizeof作用于数组名时还算你整块地盘sizeof作用于指针时只算你用来存地址的那个小格子。两者不要混为一谈。2.2 函数传参后数组名“退化”为指针现在做一道更高频的笔试/面试改编题void foo(char s[]) { printf(%zu\n, sizeof(s)); } int main() { char s[100] hello; foo(s); return 0; }函数内部sizeof(s)输出多少答案是 864 位环境不是 100。这就是 C 语言里著名的“数组参数退化”规则。虽然函数签名写的是char s[]看着像数组但在 C 语言中函数形参里的数组声明会被编译器自动调整为指针等价于void foo(char *s)。所以在函数内部s只是一个指针变量sizeof(s)也就变成了指针的大小。这个知识点会直接牵出一个笔试题高频解法错误很多同学喜欢在函数内部写sizeof(arr) / sizeof(arr[0])来求数组元素个数比如int sum_of_array(int arr[]) { size_t n sizeof(arr) / sizeof(arr[0]); // 错arr 是指针 ... }这里sizeof(arr)是指针大小sizeof(arr[0])是 int 大小两个一除在 64 位 Linux 下是 2根本不是数组元素个数。正确做法是数组定义在哪个作用域就在哪个作用域用sizeof算大小函数内部要拿数组长度必须通过参数显式传进来。2.3 字符串字面量的隐藏字符与转义陷阱再看一个细节sizeof(hello)为什么是 6因为字符串字面量在 C 语言中的类型是char[6]它真实地包含末尾的\0。strlen(hello)扫到o之后的\0停下所以返回 5。这个差异放在转义字符上会更隐蔽笔试也爱考。举个例子char s[] a\tb\n; printf(%zu %zu\n, sizeof(s), strlen(s));很多人看到\t和\n以为这是两个字符于是猜是 6。实际上\t和\n在 C 语言源码里是转义序列分别表示一个制表符和一个换行符每个只占 1 个字符。整个字符串内容是a、\t、b、\n、\0一共 5 个字节。所以sizeof(s)输出 5strlen(s)输出 4。还有更恶心的char s[] \0hello; printf(%zu %zu\n, sizeof(s), strlen(s));字符串字面量\0hello实际存储的字符序列是\0、h、e、l、l、o、\0共 7 个字节。所以sizeof(s)是 7而strlen(s)从头开始数第一个字节就是\0直接返回 0。这类题考的其实不是你能不能背出答案而是你有没有意识到\0是真实存在于内存中的一个字节sizeof会把它的位置也算进数组体积而strlen见到它就立刻收工。2.4 实际场景fgets 读完的字符串怎么安全地取长度字符串长度计算最常见的工程场景就是读取一行用户输入后判断内容。比如网上流传的“字符串逆序”练习题很多同学在 PTA 和翁恺老师的练习里都遇到过正确思路就是先用strlen拿到输入长度再做首尾交换。用fgets读入字符串时缓冲区里通常会带一个换行符需要先处理掉#include stdio.h #include string.h int main(void) { char s[101]; if (fgets(s, sizeof(s), stdin) NULL) { return 1; } size_t len strlen(s); if (len 0 s[len - 1] \n) { s[len - 1] \0; len--; } // 字符串逆序 for (size_t i 0; i len / 2; i) { char tmp s[i]; s[i] s[len - 1 - i]; s[len - 1 - i] tmp; } printf(%s\n, s); return 0; }注意这里我用sizeof(s)作为fgets的最大读取长度参数这恰好是sizeof在工程中最经典的正向应用在数组定义作用域里用sizeof(s)得到缓冲区真实容量防止越界写。但因为数组传参给函数后会退化为指针所以如果你把fgets封装到另一个函数里就必须把容量作为独立参数传进去绝不能在函数内部再用sizeof。3. 进阶陷阱表达式求值、二维数组、结构体对齐3.1 经典送命题sizeof(i) 到底加不加来看一道能把不少人打懵的题目int i 0; printf(%zu %d\n, sizeof(i), i);输出是什么答案是4 0。很多人想都不想就答4 1因为他们以为sizeof(i)会执行i让i变成 1。但sizeof的运算发生在编译期它只需要知道i这个表达式的类型是int就能算出结果是 4 个字节根本不需要在运行时把这个表达式真正执行一遍。因此i的值不会变还是 0。编译器连生成i的指令都省了直接把4当常量写进程序。这个题目是验证“sizeof 是编译期运算符”的最强证据。顺便提醒一句C 语言标准里有一条例外当sizeof作用于变长数组VLA时因为数组长度要运行时才知道sizeof确实会在运行时求值。但这不是入门笔试的重点大多数题目不会涉及你只需要知道有这么个例外存在即可答题时默认按编译期处理。3.2 二维数组的 sizeof 分解一层一层剥开二维数组的sizeof是笔试的难点但只要你记住一条原则就不怕sizeof里没有“退化”这回事数组名是什么类型就是什么类型。看例子int b[3][4];整个数组b的类型是int[3][4]共 12 个 int 元素64 位 Linux 下 int 是 4 字节所以sizeof(b) // 3 * 4 * 4 48 sizeof(b[0]) // b[0] 是第一行类型是 int[4]大小 16 sizeof(b[0][0]) // 单个元素 int大小 4再组合一下sizeof(b) / sizeof(b[0]) // 48 / 16 3得到行数 sizeof(b[0]) / sizeof(b[0][0]) // 16 / 4 4得到列数这里面隐藏着一个高频考点b[0]在绝大多数表达式里作为数组名会退化成int *比如你写func(b[0])传进去的是首元素地址。但在sizeof里b[0]依然保留第一行数组的类型int[4]所以sizeof(b[0])才能算出 16。你要是用sizeof(b[0])那又不一样了b[0]是数组指针大小是 864 位因为它是指针。分清“数组名本身用于 sizeof”和“对数组名取地址后的类型”是二维数组题型的核心。3.3 结构体字节对齐为什么不是简单相加sizeof作用在结构体上时很多人以为把成员大小加起来就行于是就有了下面的经典笔试题struct A { char c1; int x; char c2; };直觉答案1 4 1 6。实际在默认对齐规则下输出通常是12。原因是在这个结构体里int x要求按 4 字节对齐所以c1占完第 0 字节后1、2、3 三个字节被编译器填充成空隙x从第 4 字节开始放占 4、5、6、7 字节然后c2放在第 8 字节。整个结构体的大小还要对齐到成员中最大对齐数的整数倍这里是 4 的倍数所以尾部还要补到 12 字节。如果把成员顺序调换struct B { char c1; char c2; int x; };结果是 8c1和c2各自占 1 字节连续放完后x从第 4 字节开始整体大小 8。笔试里这种变式考得非常多因为它不仅考sizeof还考你对内存对齐机制的掌握程度。我建议你计算结构体大小时分三步走找出对齐要求最大的成员决定整体对齐边界按声明顺序给每个成员分配起始位置注意是否要“对齐到该成员大小整数倍”成员放完后再把整个大小补到“最大对齐数整数倍”。这个知识在嵌入式领域尤其重要因为很多硬件工程师是在用结构体映射通信协议帧结构。偏移量一旦算错接收方解出的字段就会错位。所以遇到“硬件工程师笔试题”里出现结构体大小计算请务必用这套流程而不是简单相加。3.4 malloc 出来的缓冲区千万别一上来就 strlen再看一道跟内存管理相关的坑题char *p (char *)malloc(20); printf(%zu\n, strlen(p));这段代码属于未定义行为。malloc(20)只申请了一块 20 字节的未初始化内存里面是什么没人知道。strlen(p)从这块内存的起始地址一路找\0可能在 3 个字节后碰到也可能在 19 个字节后碰到甚至可能直接撞出越界区域才碰到。结果完全不可预测甚至可能因为越界访问触发崩溃。正确做法是先给缓冲区内容做初始化再调strlenchar *p (char *)malloc(20); if (p NULL) { return 1; } p[0] \0; // 先把它变成空字符串 strcpy(p, hello); size_t len strlen(p); // 此时才安全 free(p);顺便聊一个网上经常出现的写法prt(char*)malloc(10.2*1024*sizeof(char))这类代码。它的本意是申请大概 10.2 KB 的字符空间。但实际工程里我们不建议把容量写成一个带小数的浮点表达式10.2 * 1024在 C 里会先按浮点数算出 10444.8再转成整数做参数中间可能发生截断代码表达意图也不清晰。直接写成整数容量更安全char *prt (char *)malloc(10 * 1024); // 10 KB去掉 sizeof(char) 完全没问题 char *prt2 (char *)malloc(10445); // 或者直接严格写成整数sizeof(char)在 C 标准里恒等于 1乘上去只是形式上的冗余不是错误但别被这类写法绕进去。另外在 C 语言里malloc返回的void *可以隐式转换为任意对象指针类型不需要强转但在 C 里必须强转。很多教材示例喜欢写(char *)malloc(...)这不算错不过你最好知道原因免得被问到时候支支吾吾。4. 经典笔试题实战答案和坑位一条条过这一章我把网上流传比较广的sizeof/strlen笔试题整理成一组高频列表每一题给出结论、解析和易错点。题目不算难但非常典型。4.1 高频题组逐个拆解题 1char s[] hello; printf(%zu %zu\n, sizeof(s), strlen(s));输出6 5。s是数组sizeof 把\0算进去strlen 数到终止符前停下。题 2char *p hello; printf(%zu %zu\n, sizeof(p), strlen(p));输出8 564 位环境。sizeof 算的是指针变量大小strlen 算的是字符串内容长度。题 3char s[20] hello; printf(%zu %zu\n, sizeof(s), strlen(s));输出20 5。数组容量是 20不管你实际放了多少内容strlen 拿到的是有效字符数量。题 4void foo(char s[]) { printf(%zu\n, sizeof(s)); } char str[100] hello; foo(str);输出864 位环境。数组形参退化为指针sizeof 结果与实参数组容量无关。题 5int a[10]; printf(%zu\n, sizeof(a) / sizeof(a[0]));输出10。这是sizeof最正统的求数组元素个数用法只能用在数组定义作用域内。题 6char s[] {h, e, l, l, o}; printf(%zu %zu\n, sizeof(s), strlen(s));输出sizeof(s)是 5strlen(s)是未定义行为。这里一定要瞪大眼睛s是用字符列表初始化的数组并没有自动补\0。strlen 会从第 5 字节继续向后读直到碰到某个碰巧为 0 的内存字节结果不可预测。这个题目是区分“懂原理”和“背答案”的试金石。题 7int i 0; printf(%zu %d\n, sizeof(i), i);输出4 0。sizeof是编译期运算符不执行副作用。题 8struct A { char c1; int x; char c2; }; printf(%zu\n, sizeof(struct A));输出常见环境为12。按成员对齐规则分配并补齐不能简单相加。题 9int b[3][4]; printf(%zu %zu %zu\n, sizeof(b), sizeof(b[0]), sizeof(b[0][0]));输出48 16 464 位 Linux。注意b[0]在 sizeof 里保持第一行数组类型。4.2 笔试答题速查表为了方便你考试前快速过一遍我把常见场景整理成一张速查表代码场景sizeof 结果strlen 结果char s[] hello65char s[20] hello205char *p hello864 位指针5char s[] {a,b,c}3未定义行为char s[] \0hello70int a[10]40不可用struct{char c; int x;}8 取决于对齐规则不可用最后强调一句表里的指针大小、int 大小、结构体对齐结果都与平台相关。考试时如果题目没有特别说明一般默认按主流 64 位平台作答如果题干写了 32 位环境指针大小就要改成 4。很多人在答案里写“sizeof(p) 8”却不说明平台容易和面试官产生歧义。你答题时主动补一句“64 位下是 8”反而显得基本功扎实。5. 动手实测用 VSCode GCC 搭一个验证环境5.1 环境准备与第一个测试程序网上关于“vscode 配置 c 语言环境”的帖子很多实际可以很简单。你需要两样东西一个 C 编译器推荐 GCC。Windows 上可以装 MinGW-w64macOS 上可以用clang它是默认编译器命令略有不同但兼容Linux 上直接sudo apt install gcc即可。VSCode 的 C/C 扩展由微软发布的那个ms-vscode.cpptools。它负责智能提示、语法高亮和调试。简单到可以在终端直接编译运行不一定非要折腾 IDE 配置。写完代码存成main.c然后在终端执行gcc main.c -o main ./main如果只是想验证sizeof/strlen的各种输出把测试代码写好敲这一条命令就够了。VSCode 对于这类小程序的意义主要是查看函数定义、类型信息比较方便调试时还能用图形界面断点。下面这个程序几乎覆盖了上面所有考点#include stdio.h #include string.h #include stddef.h struct Test { char c1; int x; char c2; }; void foo(char s[]) { printf(in foo: sizeof(s)%zu\n, sizeof(s)); } int main(void) { char s1[] hello; char *p1 hello; char s2[20] hello; char s3[] {h, e, l, l, o}; char s4[] \0hello; char s5[] a\tb\n; int a[10]; int b[3][4]; int i 0; printf(sizeof(s1)%zu, strlen(s1)%zu\n, sizeof(s1), strlen(s1)); printf(sizeof(p1)%zu, strlen(p1)%zu\n, sizeof(p1), strlen(p1)); printf(sizeof(s2)%zu, strlen(s2)%zu\n, sizeof(s2), strlen(s2)); printf(sizeof(s3)%zu, strlen(s3)%zu -- 注意 s3 没有 \\0\n, sizeof(s3), strlen(s3)); printf(sizeof(s4)%zu, strlen(s4)%zu\n, sizeof(s4), strlen(s4)); printf(sizeof(s5)%zu, strlen(s5)%zu\n, sizeof(s5), strlen(s5)); printf(sizeof(a)%zu, 元素个数%zu\n, sizeof(a), sizeof(a) / sizeof(a[0])); printf(sizeof(b)%zu, sizeof(b[0])%zu, sizeof(b[0][0])%zu\n, sizeof(b), sizeof(b[0]), sizeof(b[0][0])); printf(sizeof(i)%zu, i%d\n, sizeof(i), i); printf(struct Test size%zu\n, sizeof(struct Test)); char buf[100] hello; foo(buf); return 0; }因为strlen(s3)是未定义行为不同编译器可能给出不同结果甚至可能崩溃。你运行后会发现它基本能返回一个看起来“正常”的数字比如 6 或 7那是因为所在内存区域恰好有零字节。千万别把这个结果当成标准答案它只是运气好而已。5.2 打印 size_t 的格式符踩坑上面代码里我用的是%zu这是 C99 标准专门为size_t提供的格式占位符。sizeof和strlen的返回值类型都是size_t在 64 位系统上它是 8 字节无符号整数。很多同学习惯写%d在 64 位平台上编译可能会报警告因为%d期望一个int而size_t是unsigned long这个不匹配属于未定义行为。轻则输出错误重则栈上参数读取异常。如果你用的编译器比较老不支持%zu可以手动强转printf(%lu\n, (unsigned long)sizeof(s)); printf(%llu\n, (unsigned long long)sizeof(s));推荐写%zu既干净又不丢信息。考试如果出到打印题默认环境通常支持放心用。5.3 调试技巧用调试器看 sizeof 干扰结果还有一种更直观的验证方法直接进调试器看。VSCode 里配上调试器后在printf那行打断点鼠标悬停在sizeof(s1)上调试插件可能会显示6。如果你把s1传进foo函数再中断会看到形参s的类型已经被推理成指针。命令行选手可以这样gcc -g main.c -o main gdb ./main break main print sizeof(s1)用 GDB 打印sizeof(s1)输出$1 6。再下一行打印strlen(s1)输出$2 5。亲眼看到这些数字可比死背表格有说服力得多。6. 面试官视角这类题到底在考什么6.1 结构化答题话术面试时如果被问到“说下 sizeof 和 strlen 的区别”我不建议你上来就背列表。比较讨巧的回答方式是分三层展开第一层说身份sizeof是运算符、关键字编译期求值不需要头文件strlen是函数运行期执行需要string.h。第二层说语义sizeof计算对象或类型占用的内存字节数不在乎里面存的是什么strlen计算以\0结尾的字符串中有效字符个数碰不到终止符就永远不知道什么时候停。第三层举例子比如char s[] hellosizeof(s)是 6strlen(s)是 5但换成char *p hellosizeof(p)在 64 位环境下是 8strlen(p)还是 5。这个例子能同时说明“数组和指针的区别”“sizeof 对数组名保留整体类型”“strlen 只关心指向内容”三个知识点。这样回答逻辑清晰面试官基本没法再追问到你卡壳。6.2 常见误区快查把容易错的认知整理成一个误区表考前最后瞄一眼常见误区正确理解sizeof 是函数要加头文件sizeof 是关键字不需要头文件sizeof 返回字符串长度sizeof 返回内存占用字节数与字符串长度无关strlen 会计算数组全部内容strlen 只计算到\0前的内容数组中没有\0就会越界数组传入函数后还能用 sizeof 求大小数组形参退化为指针sizeof 只能拿到指针大小sizeof(i) 会执行自增sizeof 在编译期求值不执行表达式副作用结构体大小等于成员大小之和要考虑内存对齐、填充字节、尾部补齐结果在 32 位和 64 位平台完全一样指针大小、部分数据类型大小、结构体对齐结果会随平台变化6.3 最后一个小技巧先看类型再看结束符我个人在实际面试和辅导中积累的经验是所有关于sizeof和strlen的题目都可以用十个字概括解题步骤“先看类型再看结束符。”看到sizeof先问自己这个操作数是什么类型是数组名、指针、结构体还是普通变量类型确定后sizeof的本质就是返回这个类型的字节数。看到strlen先问这个内存区域是不是真的以\0结尾数组初始化方式决定了它是否隐藏着终止符指针指向的字符串是否合法。只要这两个问题有答案题目基本就不会错。我当时学这个知识点时也经历过“背了三天题换个新题又错”的阶段。后来把所有情况写成一个printf全家桶程序在本地跑了一遍亲眼看到输出结果才真正把这些知识点内化。所以在这里也建议你花一个晚上把上面清单敲一遍、跑一遍、故意猜错几回。踩过坑的印象永远比死记硬背深刻得多。