本文整理了 10 道进阶指针选择题,全部源于真实工程应用场景。每题后面都有详细解析,帮助你彻底掌握指针的细节。最后附上一个 数组与指针的易错总结表


1. 指针差值

uint8_t buf[10];
uint8_t *p = &buf[2];
uint8_t *q = &buf[7];
printf("%td\n", q - p);

输出是多少?
A. 5
B. 7
C. 10
D. 未定义

答案:A. 5

解析:

  • q - p 的结果是“两个指针之间相差多少个元素”。

  • p = &buf[2]q = &buf[7],相差 5 个 uint8_t 元素。

  • 因此输出 5

  • 注意:差值不是字节数,而是以元素大小为单位。


2. sizeof 与数组指针

uint8_t arr[5] = {1,2,3,4,5};
uint8_t *p = arr;
printf("%zu %zu %zu\n", sizeof(arr), sizeof(p), sizeof(&arr));

在 32 位 MCU 上输出为:
A. 5 5 5
B. 5 4 4
C. 5 4 20
D. 5 4 8

答案:B. 5 4 4

解析:

  • sizeof(arr):数组大小 = 5 * 1 = 5

  • sizeof(p):指针大小,在 32 位系统上是 4。

  • sizeof(&arr):类型是 uint8_t (*)[5],是“指向整个数组的指针”,本质仍然是一个指针,所以大小是 4。

👉 如果写 sizeof(*(&arr)),那就等价于 sizeof(arr),结果是 5
这是面试里非常容易出错的细节。


3. 指针运算与越界

uint8_t buf[4] = {10,20,30,40};
uint8_t *p = buf + 4;
printf("%d\n", *(p-1));

输出为:
A. 30
B. 40
C. 越界错误
D. 未定义行为

答案:B. 40

解析:

  • buf + 4 指向数组末尾的“下一个位置”(C 标准允许这样)。

  • *(p-1) 等价于 buf[3] = 40

  • 所以输出 40

  • 注意:如果直接 *p 就是越界未定义。


4. const 修饰的陷阱

const uint8_t data[3] = {11,22,33};
const uint8_t *p = data;
uint8_t *q = (uint8_t*)p;
*q = 99;
printf("%d\n", data[0]);

输出结果是:
A. 11
B. 99
C. 未定义行为
D. 编译错误

答案:C. 未定义行为

解析:

  • data 是只读存储区(通常放在 Flash 或只读内存)。

  • 强转 (uint8_t*)p 并写入是非法的。

  • 有的编译器/硬件可能让你看到 99,有的可能直接崩溃(HardFault)。

  • C 标准明确规定:修改 const 对象是 未定义行为


5. 结构体与指针偏移

typedef struct {
    uint16_t id;
    uint8_t status;
    uint8_t len;
} Msg;

Msg m = {0x1234, 0xAA, 5};
uint8_t *p = (uint8_t*)&m;
printf("%02X\n", *(p+2));

小端 32 位 MCU 上输出为:
A. 12
B. 34
C. AA
D. 05

答案:C. AA

解析:

  • 小端:id = 0x1234 在内存里是 34 12

  • 结构体内存排布:

    • p[0] = 0x34

    • p[1] = 0x12

    • p[2] = 0xAA

    • p[3] = 0x05

  • 所以 *(p+2) = 0xAA


6. 指针与 memcpy

uint8_t src[4] = {1,2,3,4};
uint8_t dst[4];
uint8_t *p = src + 1;
memcpy(dst, p, 2);
printf("%d %d\n", dst[0], dst[1]);

输出为:
A. 1 2
B. 2 3
C. 3 4
D. 2 4

答案:B. 2 3

解析:

  • p = src+1 → 指向 src[1] = 2

  • memcpy(dst, p, 2) → 拷贝 23dst[0], dst[1]

  • 输出 2 3


7. 指针与 volatile

volatile uint8_t reg = 0x55;
uint8_t *p = (uint8_t*)®
*p = 0xAA;
printf("%02X\n", reg);

输出为:
A. 55
B. AA
C. 未定义
D. 编译错误

答案:B. AA

解析:

  • volatile 告诉编译器:reg 可能随时被硬件修改,不要优化。

  • 这里 *p = 0xAA 确实修改了该内存位置,reg 的值也随之改变。

  • 所以输出 0xAA

  • 工程场景:寄存器映射常用 volatile


8. 指针与函数参数

void func(uint8_t *p) {
    *p = 77;
}

int main() {
    uint8_t x = 10;
    func(&x);
    printf("%d\n", x);
}

输出为:
A. 10
B. 77
C. 随机值
D. 编译错误

答案:B. 77

解析:

  • func(&x)x 的地址传进去。

  • 在函数里 *p = 77 直接修改了 main 里的 x

  • 所以最后输出 77

  • 工程里这就是 指针作输出参数 的经典用法。


9. 字符串与指针

char *s = "Hello";
char t[] = "Hello";
printf("%c %c\n", *(s+1), *(t+1));

输出为:
A. H H
B. e e
C. e H
D. 编译错误

答案:B. e e

解析:

  • s = "Hello"s 指向字符串常量(只读区)。

  • t[] = "Hello"t 是数组,存在栈上。

  • *(s+1)*(t+1) 都是 'e'

  • 所以输出 e e

  • 注意:s[0] 可读不可写,而 t[0] 可读可写。


10. 指针类型转换与对齐

uint32_t val = 0x11223344;
uint8_t *p = (uint8_t*)&val;
printf("%02X\n", *(p+1));

小端存储 的 MCU 上输出为:
A. 11
B. 22
C. 33
D. 44

答案:C. 33

解析:

  • 小端存储:低字节在低地址。

  • 内存排布:44 33 22 11

  • p[0] = 0x44

  • p[1] = 0x33

  • p[2] = 0x22

  • p[3] = 0x11

  • 所以 *(p+1) = 0x33


🔑 易错点总结表

表达式 类型 结果 (32 位) 说明
sizeof(arr) 数组大小 数组总字节数 不会退化成指针
sizeof(p) 指针 (uint8_t*) 4 指针大小
sizeof(&arr) 指向数组的指针 4 本质还是指针
sizeof(*(&arr)) 数组本身 数组总字节数 常被误解

✅ 总结

这一组题涵盖了:

  • 数组 vs 指针 的区别(题 2)

  • 内存布局和大小端(题 5、10)

  • const 与 volatile 限定(题 4、7)

  • 函数指针参数的作用(题 8)

  • 常见指针运算和陷阱(题 1、3、6)

建议在做题时多画内存示意图,帮助理解指针和数组之间的关系。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐