内存操作 中

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 1 Likes: 0 Comments: 0

356.2.3.2 内存操作(中)

/* 引入 printf 函数的声明。 */
#include <stdio.h>
/* 引入 memset 函数的声明。 */
#include <string.h>

/* 定义本节继续使用的内存填充测试函数。 */
void Memset_Test()
{
    /* 定义含 15 个 int 元素的局部数组。 */
    int buf[15];
    /* 输出当前测试项目的提示文字。 */
    printf("memset test func\r\n");
    /* 输出课程计算的元素个数和 int 大小;原代码的 %ld 并非 size_t 的可移植格式。 */
    printf("sizeof buf byte num:%ld, int size:%ld\r\n", sizeof(buf) / sizeof(int), sizeof(int));

    /* 视频末尾暂时注释了填充操作。 */
    // memset(buf, 0, sizeof(buf));

    /* 视频末尾暂时注释了原来的遍历循环。 */
    // for (char i = 0; i < sizeof(buf); i++)
    /* 这是被注释循环原来的左花括号。 */
    // {
    /* 这是被注释循环原来的元素输出语句。 */
    //     printf("%d ", buf[i]);
    /* 这是被注释循环原来的右花括号。 */
    // }
    /* 视频末尾也暂时注释了循环后的换行。 */
    // printf("\r\n");
}

/* 程序从 main 函数开始执行。 */
int main()
{
    /* 输出整个内存函数实验的标题。 */
    printf("GNU Memory Function Test\r\n");

    /* 调用上面定义的测试函数。 */
    Memset_Test();
    /* 返回 0,表示程序正常结束。 */
    return 0;
}

上面是本节视频结束时的 main.c。课程先用 char buf[15] 验证 sizeof 和 memset,随后暂时注释填充与遍历代码,把数组改成 int buf[15],专门观察数组总字节数、单个元素大小和元素个数之间的关系。本节尚未开始 memcmp、memcpy 或 memmove,因此没有提前收录配套源码中的后续函数。

顶部代码严格保留了视频中的 %ld。但 sizeof 的结果类型是 size_t,并不保证等于 long int;可移植的输出格式应为 %zu。这一错误会在正文中详细说明。

一、实际项目内存操作

上一节把数组长度 8 同时写在声明、memset 和循环条件中。一旦把数组扩为 15 个元素,就必须修改多处数字,容易漏改。视频因此把代码逐步改为使用 sizeof。

本节实验经历了三个阶段:

阶段数组声明与表达式视频平台结果说明
1char buf[15]、sizeof(buf)15char 的大小按定义就是 1 个 C 字节
2int buf[15]、sizeof(buf)60该平台的 sizeof(int) 为 4,所以总大小为 15 × 4
3sizeof(buf) / sizeof(int)15总字节数除以单个元素的字节数,得到元素个数

这里的函数名和关键字是 sizeof,不是转录中的“size off”;它是 C 语言运算符,不是运行库函数,所以更准确的说法是“sizeof 表达式产生一个结果”,而不是“函数返回”。

sizeof 有两种常见写法:

sizeof expression
sizeof(type_name)

对表达式使用时,括号通常不是必需的,因此 sizeof buf 与 sizeof(buf) 等价;对类型名使用时必须写括号,例如 sizeof(int)。

sizeof 产生其操作数所占的 C 字节数,结果类型为 size_t。size_t 是足以表示对象大小的无符号整数类型,具体底层类型由实现决定:可能是 unsigned int、unsigned long 或其他无符号整数类型,不能写死成“long unsigned int”。

视频第一次写成:

printf("sizeof buf len:%d\r\n", sizeof(buf));

GCC 警告 %d 需要 int,而实参是当前平台上的 long unsigned int。老师随后改成 %ld,虽然画面中的数值能正常显示,但 %ld 要求的实参是 long int,与 unsigned long 仍然不是同一类型。可变参数函数不会替调用者自动修复这种格式类型不匹配;严格来说,格式说明与实参类型不匹配会导致未定义行为。

C99 为 size_t 定义了 z 长度修饰符,正确、可移植的写法是:

printf("sizeof buf:%zu, int size:%zu\r\n", sizeof(buf), sizeof(int));

顶部最终代码中的除法结果仍是 size_t,因此同样应使用 %zu:

printf("element count:%zu, element size:%zu\r\n",
       sizeof(buf) / sizeof(buf[0]),
       sizeof(buf[0]));

后一个版本还把 sizeof(int) 改成 sizeof(buf[0])。这样即使以后把数组元素类型从 int 改成别的类型,元素个数公式也会自动保持一致。

需要进一步理解 sizeof 的几个边界:

  • C 语言规定 sizeof(char) == 1,所以 char buf[15] 占 15 个 C 字节;一个 C 字节包含多少位由 CHAR_BIT 给出,标准要求至少 8 位,常见 GNU/Linux 平台为 8 位。
  • sizeof(int) 是实现定义结果。视频所用 Ubuntu/目标 ABI 得到 4,不能据此断言“所有 Linux 的 int 永远都是 4 字节”。标准只规定基本整数类型之间的最小范围和大小顺序。
  • 对普通定长数组,本例的 sizeof(buf) 通常可在翻译时确定,且不会读取数组元素。对变长数组(VLA),大小依赖运行时长度,相关 sizeof 表达式可能在运行时求值。
  • sizeof 的单位始终是字节,不直接表示物理内存页、CPU 字长或数组元素个数。

视频使用的编译和运行命令仍是:

gcc main.c -o main
./main

建议学习阶段打开更多诊断:

gcc -std=c99 -Wall -Wextra -Wformat=2 main.c -o main

-Wall 会启用常用警告,其中包括 -Wformat;若还希望专门检查格式字符串的有符号/无符号不匹配,可再启用 -Wformat-signedness。警告不一定阻止生成程序,但不应因为“仍能运行”就忽略它。

二、内存操作注意事项

对于真正的数组对象,元素个数的通用计算式是:

[ \text{元素个数} = \frac{\operatorname{sizeof}(\text{整个数组})}{\operatorname{sizeof}(\text{一个元素})} ]

对应 C 代码为:

size_t count = sizeof(buf) / sizeof(buf[0]);

以视频最终的 int buf[15] 为例,在该平台上:

[ \operatorname{sizeof}(buf)=15\times\operatorname{sizeof}(int)=15\times4=60 ]

所以:

[ 60\div4=15 ]

视频最后仍把输出标签写成 sizeof buf byte num,但实际传入的第一个值已经是 sizeof(buf) / sizeof(int),即元素个数 15,不再是字节数 60。变量名、日志标签和实际含义必须保持一致,否则调试输出会误导读者。

同一个 sizeof(buf) 在不同位置承担的含义也不同:

memset(buf, 0, sizeof(buf));

memset 的第三个参数本来就要求字节数,所以填充整个数组时直接传 sizeof(buf) 是正确的。相反,遍历数组时循环次数应是元素个数:

for (size_t i = 0; i < sizeof(buf) / sizeof(buf[0]); i++) {
    printf("%d ", buf[i]);
}

如果把 int buf[15] 的 sizeof(buf)(视频平台上为 60)直接当作循环次数,循环会访问 buf[15] 到 buf[59],严重越界。越界读写属于未定义行为,可能破坏其他局部数据或造成崩溃。

课程原循环使用 char i。数组较小时可以计数,但不适合作为通用数组下标:char 的范围有限,而且与无符号的 size_t 比较时会发生整数转换。与数组大小和元素个数配套的下标类型通常应使用 size_t。

数组与指针是本公式最容易踩的坑

元素个数公式只在 buf 仍是数组对象的作用域中成立。数组作为函数实参传递时,形参声明中的数组形式会调整为指针:

void print_array(int buf[])
{
    /* 此处的 sizeof(buf) 是指针大小,不是调用者数组大小。 */
}

即使形参写成 int buf[15],函数体内的 buf 仍按 int * 处理,sizeof(buf) / sizeof(buf[0]) 得不到 15。正确设计是由调用者把元素个数一并传入:

void print_array(const int *buf, size_t count);

动态分配得到的内存也只有一个指针,sizeof(pointer) 同样无法恢复分配时的字节数或元素个数,程序必须另外保存长度。

memset 与整数数组

对视频中的 int buf[15] 执行:

memset(buf, 0, sizeof(buf));

会把整个数组对象表示的所有字节清零;对于标准整数类型,这可得到数值 0。若第二个参数改为非零值,例如 2,则每个字节都会写成 0x02,并不是把每个 int 元素赋值为整数 2。给整数数组设置任意非零值时,应使用逐元素赋值,而不能把 memset 当作通用的数组赋值函数。

参考资料