【深入理解指针(5)】 —— sizeof与strlen的对比及指针笔试题解析

在这里插入图片描述

本文深入解析C语言中sizeof与strlen的关键区别,并通过一系列典型指针笔试题帮助读者掌握指针在数组、字符串和内存运算中的应用技巧。

一、sizeof和strlen的对比

1.1 sizeof操作符

sizeof是C语言中的单目操作符,用于计算变量或类型所占内存空间的大小(单位为字节)。它有以下重要特性:

  • 计算对象可以是变量或类型

  • 不关心内存中存储的具体数据

  • 在编译期间计算结果

  • 对数组名使用sizeof会返回整个数组的大小

#include <stdio.h>

int main() {
    int a = 10;
    printf("%zu\n", sizeof(a));    // 4 (int类型大小)
    printf("%zu\n", sizeof a);      // 4 (可省略括号)
    printf("%zu\n", sizeof(int));   // 4 (类型大小)
    
    double arr[10];
    printf("%zu\n", sizeof(arr));  // 80 (10*8=80字节)
    return 0;
}

1.2 strlen函数

strlen是C标准库函数(需包含 <string.h> ),用于计算字符串的长度不包含结束符’\0’)。关键特性包括:

  • 从给定地址开始向后扫描,直到遇到’\0’

  • 如果没有找到’\0’,会继续向后扫描,可能导致越界访问

  • 只能用于以’\0’结尾的有效字符串

#include <stdio.h>
#include <string.h>

int main() {
    char arr1[3] = {'a', 'b', 'c'}; // 无结束符
    char arr2[] = "abc";            // 自动添加'\0'
    
    printf("%zu\n", strlen(arr1));  // 未定义行为(可能输出随机值)
    printf("%zu\n", strlen(arr2));  // 3 (有效字符串)
    return 0;
}

1.3 sizeof与strlen对比总结

特性sizeofstrlen
本质编译时操作符运行时库函数
参数变量/类型字符串指针
计算内容内存占用大小'\0'前的字符数量
是否关心内存数据是(依赖'\0'
数组名作为参数返回整个数组大小退化为指针
安全性安全可能越界访问

二、数组和指针笔试题解析

只有sizeof(数组名) &数组名才表示整个数组

2.1 一维数组解析

int a[] = {1,2,3,4};

//64位系统
printf("%zu\n", sizeof(a));      // 16 - 整个数组大小(4个int*4)
printf("%zu\n", sizeof(a+0));    // 8  - 首元素地址
printf("%zu\n", sizeof(*a));     // 4  - 第一个元素(int)
printf("%zu\n", sizeof(a[1]));   // 4  - 第二个元素(int)
printf("%zu\n", sizeof(&a));     // 8  - 数组地址(指针)
printf("%zu\n", sizeof(&a+1));   // 8  - 跳过整个数组后的地址
printf("%zu\n", sizeof(&a[0]));  // 8  - 首元素地址
printf("%zu\n", sizeof(&a[0]+1));// 8  - 第二个元素地址

2.2 字符数组解析

代码1:sizeof应用

char arr[] = {'a','b','c','d','e','f'};

printf("%zu\n", sizeof(arr));     // 6 - 整个数组大小
printf("%zu\n", sizeof(arr+0));   // 8 - 首元素地址
printf("%zu\n", sizeof(*arr));    // 1 - 第一个元素(char)
printf("%zu\n", sizeof(arr[1]));  // 1 - 第二个元素
printf("%zu\n", sizeof(&arr));    // 8 - 数组地址
printf("%zu\n", sizeof(&arr+1));  // 8 - 跳过整个数组后的地址
printf("%zu\n", sizeof(&arr[0]+1));//8 - 第二个元素地址

代码2:strlen应用(危险!)

char arr[] = {'a','b','c','d','e','f'}; // 无结束符

printf("%zu\n", strlen(arr));     // 未定义!(可能一直找到内存中的'\0')
printf("%zu\n", strlen(arr+0));   // 同上
printf("%zu\n", strlen(*arr));    // 编译错误!(参数应为char*)
// 其他行类似,都会导致问题

让我们重点分析第三个代码片段让我们重点分析第三个代码片段
在这里插入图片描述
strlen库函数可以看出,其参数是char* str,即传入的是地址。而*arr表示的是第一个元素'a',这是一个字符而非地址。当字符'a'被自动转换为ASCII值97时,系统会尝试将这个数值作为内存地址访问。由于我们没有权限访问这个地址,程序就会崩溃终止。

代码3:有效字符串sizeof

char arr[] = "abcdef"; // 包含隐含的'\0'

printf("%zu\n", sizeof(arr));     // 7 - 6字符+1个'\0'
printf("%zu\n", sizeof(arr+0));   // 8 - 首元素地址
printf("%zu\n", sizeof(*arr));    // 1 - 首字符
printf("%zu\n", sizeof(arr[1]));  // 1 - 第二个字符
printf("%zu\n", sizeof(&arr));    // 8 - 数组地址
printf("%zu\n", sizeof(&arr+1));  // 8 - 跳过整个数组
printf("%zu\n", sizeof(&arr[0]+1));//8 - 第二个字符地址

** 代码4:有效字符串strlen**

char arr[] = "abcdef"; // 包含隐含的'\0'

printf("%zu\n", strlen(arr));      // 6 - 到'\0'前的字符数
printf("%zu\n", strlen(arr+0));    // 6 - 同上
// printf("%zu\n", strlen(*arr));  // 错误!参数类型不匹配
// printf("%zu\n", strlen(arr[1]));// 错误!
printf("%zu\n", strlen(&arr));     // 6 - 与arr相同
printf("%zu\n", strlen(&arr+1));   // 未定义!(跳过整个数组)
printf("%zu\n", strlen(&arr[0]+1));// 5 - 从b开始计算

2.3 二维数组解析

int a[3][4] = {0};

printf("%zu\n", sizeof(a));        // 48 (3*4*4)
printf("%zu\n", sizeof(a[0][0]));  // 4 (int)
printf("%zu\n", sizeof(a[0]));     // 16 (第一行数组大小)
printf("%zu\n", sizeof(a[0]+1));   // 8 (第一行第二个元素地址)
printf("%zu\n", sizeof(a+1));      // 8 (第二行地址)
printf("%zu\n", sizeof(&a[0]+1));  // 8 (第二行地址)
printf("%zu\n", sizeof(*a));       // 16 (第一行数组大小)
printf("%zu\n", sizeof(a[3]));     // 16 (类型大小,不访问内存)

在之前的文章中我们讨论过二维数组的指针问题。其中,a[i]代表数组名,即二维数组中某行对应的一维数组名称。因此,sizeof(a[i])&a[i]都是获取整个一维数组的地址。

数组名意义总结:

  • sizeof(数组名):整个数组的大小

  • &数组名:整个数组的地址

  • 其他情况:退化为首元素地址

三、指针运算笔试题解析

3.1 题目1:数组指针偏移

int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1)); // 2,5

解析:

  • a+1:首元素地址+1 → 第二个元素(2)

  • &a+1:跳过整个数组 → 指向数组末尾后

  • ptr-1:回退一个int → 指向最后一个元素(5)

3.2 题目2:结构体指针运算

struct Test {
    int Num;
    char *pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p = (struct Test*)0x100000;

//结构体假设20个字节
//十六进制0x1代表1
int main() {
    printf("%p\n", p + 0x1);             // 0x100014 (+20字节,20转化为十六进制14)
    printf("%p\n", (unsigned long)p + 0x1); // 0x100001
    printf("%p\n", (unsigned int*)p + 0x1); // 0x100004
    return 0;
}

结构体指针加1会跳过整个结构体的大小

解析:

  • 指针+1:增加类型大小(20→0x14)

  • 数值+1:增加1

  • 整型指针+1:增加4字节

3.3 题目3:逗号表达式陷阱

int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p = a[0];
printf("%d", p[0]); // 1

解析:

  • 逗号表达式返回最后一个值

  • 实际初始化:{1, 3, 5}(其他为0)

  • a[0]指向第一行{1,3}(内存布局:{ {1,3},{5,0},{0,0} }

  • p[0]访问第一个元素1

3.4 题目4:数组指针类型差异

int a[5][5];
int(*p)[4] = a; // 类型为int(*)[4]
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); 
// FFFFFFFC,-4

在这里插入图片描述

从图中可见,&p[4][2]&a[4][2]之间相差4个int*的内存空间。由于指针相减得到的是元素间隔数,且&a[4][2]地址高于&p[4][2],因此&p[4][2] - &a[4][2] = -4printf("%d")输出结果)。当使用printf("%p")打印地址时,系统会将整数值-4转换为十六进制形式。在补码表示中,-4对应二进制11111111 11111111 11111111 11111100,转换为十六进制即为FFFFFFFC

3.5 题目5:二维数组指针

int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};
int *ptr1 = (int *)(&aa + 1);     // 跳过整个数组
int *ptr2 = (int *)(*(aa + 1));   // 第二行首地址
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); // 10,5

解析

  • &aa+1:指向数组末尾后

  • ptr1-1:回退一个int → 最后一个元素10

  • aa+1:第二行地址

  • *(aa+1):第二行首元素(6)

  • ptr2-1:第一行最后一个元素(5)

3.6 题目6:指针数组

char *a[] = {"work","at","alibaba"};
char **pa = a;
pa++;
printf("%s\n", *pa); // "at"

解析:

  • a:指针数组,元素为char*

  • pa指向第一个指针(“work”)

  • pa++后指向第二个指针(“at”)

  • *pa解引用得到"at"

3.7 题目7:三级指针应用

char *c[] = {"ENTER","NEW","POINT","FIRST"};
char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp;

printf("%s\n", **++cpp);       // POINT
printf("%s\n", *--*++cpp+3);   // ER
printf("%s\n", *cpp[-2]+3);    // ST
printf("%s\n", cpp[-1][-1]+1); // EW

解析:

  • ++cpp指向cp[1]→c+2

    • **++cpp→*(c+2)→"POINT"
  • 再次++cpp指向cp[2]→c+1

    • --*++cpp→–(c+1)→c+0

    • *--*++cpp→*(c+0)→"ENTER"

    • +3→"ER"

  • cpp[-2]→*(cpp-2)→cp[0]→c+3

    • *cpp[-2]→*(c+3)→"FIRST"

    • +3→"ST"

  • cpp[-1]→*(cpp-1)→cp[1]→c+2

    • cpp[-1][-1]→*(c+2-1)→*(c+1)→"NEW"

    • +1→"EW"

四、总结

掌握指针的核心要点:

  • 理解指针类型决定偏移量

  • 区分数组名在sizeof、&操作中的特殊含义

  • 明确sizeof是编译时操作,strlen是运行时函数

  • 注意指针运算的单位由基类型决定

  • 多维数组本质是"数组的数组"

透彻理解这些概念将帮助你在C语言编程中游刃有余地处理指针和内存操作。
如果觉得内容对你有帮助,别忘了点赞 + 收藏,你的支持是持续分享的动力~ 咱们下篇技术文章再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值