sizeof在C语言中到底如何计算变量大小?

99ANYc3cd6
预计阅读时长 16 分钟
位置: 首页 C语言 正文

sizeof 是 C 语言中一个非常重要的单目运算符,而不是一个函数,它的作用是返回一个对象或类型所占用的内存字节数。

c语言sizeof
(图片来源网络,侵删)

sizeof 的基本语法和用途

sizeof 有两种主要的使用形式:

sizeof (类型名)

这种形式用于获取某个类型在内存中占用的字节数。

printf("int 的大小: %zu 字节\n", sizeof(int));
printf("char 的大小: %zu 字节\n", sizeof(char));
printf("double 的大小: %zu 字节\n", sizeof(double));
printf("指针的大小: %zu 字节\n", sizeof(int*)); // 指针的大小取决于系统架构(32位或64位)

注意:

  • printf 中,%zu 是用于打印 size_t 类型(sizeof 返回的类型)的正确格式说明符,在一些旧的编译器上可能不支持,可以使用 %lu 并强制转换为 unsigned long,但 printf("%zu", ...) 是现代 C 标准(C99 及以后)推荐的方式。

sizeof 表达式

这种形式用于获取某个变量数组结构体等表达式所占用的内存字节数,括号 在对变量使用时是可选的,但加上是更安全、更清晰的做法。

c语言sizeof
(图片来源网络,侵删)
int a = 10;
printf("变量 a 的大小: %zu 字节\n", sizeof(a)); // 或者 sizeof(a)
printf("变量 a 的大小: %zu 字节\n", sizeof a);   // 语法正确,但不推荐
int arr[10];
printf("数组 arr 的大小: %zu 字节\n", sizeof(arr)); // 返回整个数组的大小 (10 * sizeof(int))

sizeof 的工作原理:编译时计算

sizeof 的一个关键特性是:它的值在编译期间就已经确定了,而不是在运行时计算的

这意味着:

  • sizeof 不会导致你的程序真正去访问内存、创建对象或执行任何运行时操作。
  • 编译器在编译代码时,会根据变量的类型和声明,直接计算出 sizeof 的结果,并用这个常量值替换掉 sizeof 的调用。

示例:

int arr[10];
int size = sizeof(arr); // 编译器直接计算出 10 * 4 = 40 (假设 int 是 4 字节)
                       // 然后生成 movl $40, -4(%rbp) 这样的汇编指令
                       // 而不是在运行时循环遍历数组来计算大小。

sizeof 对不同数据对象的应用

a. 基本数据类型

大小取决于编译器和操作系统架构(通常是 32 位或 64 位)。

c语言sizeof
(图片来源网络,侵删)
  • char: 1 字节
  • short: 2 字节
  • int: 4 字节
  • long: 在 32 位系统上是 4 字节,在 64 位系统上是 8 字节(不绝对,需查阅编译器文档)
  • long long: 8 字节
  • float: 4 字节
  • double: 8 字节
  • long double: 8 或 16 字节
  • 指针 (): 在 32 位系统上是 4 字节,在 64 位系统上是 8 字节。

b. 数组

sizeof 返回的是整个数组在内存中占用的总字节数,即 元素个数 * 单个元素的大小

int arr[5];
printf("%zu\n", sizeof(arr)); // 输出 20 (int 是 4 字节)

一个非常重要的陷阱:数组作为函数参数 当数组作为函数参数传递时,它会退化为指向其第一个元素的指针,在函数内部使用 sizeof 得到的不再是数组的大小,而是指针的大小。

#include <stdio.h>
void print_array_size(int arr[]) { // int arr[] 等价于 int *arr
    printf("函数内部 arr 的大小: %zu\n", sizeof(arr)); // 输出的是指针的大小 (8)
}
int main() {
    int arr[10];
    printf("函数外部 arr 的大小: %zu\n", sizeof(arr)); // 输出 40 (10 * 4)
    print_array_size(arr);
    return 0;
}

如果需要在函数内部知道数组的大小,必须额外将数组的大小作为参数传递进去。

c. 结构体

sizeof 返回的是结构体的总大小,这个大小可能会因为内存对齐的原因,大于所有成员大小之和。

struct MyStruct {
    char c;      // 1 字节
    int i;       // 4 字节
    double d;    // 8 字节
};
printf("%zu\n", sizeof(struct MyStruct)); // 输出可能是 24,而不是 1+4+8=13

内存对齐: 为了让 CPU 能更高效地访问数据,编译器会在结构体成员之间填充一些字节,使得每个成员的起始地址都能被其自身大小的整数倍整除。

  • char c 占 1 字节,从偏移量 0 开始。
  • int i 需要 4 字节对齐,所以编译器会在 char c 后面填充 3 个字节,int i 从偏移量 4 开始。
  • double d 需要 8 字节对齐,它从偏移量 8 开始。
  • 结构体总大小也需要是最大成员(这里是 double,8 字节)的整数倍,当前总大小是 1+3+4+8 = 16,已经是 8 的倍数,sizeof(struct MyStruct) 是 16。

d. 联合体

联合体的大小是其最大成员的大小,因为所有成员共享同一块内存。

union MyUnion {
    char c;
    int i;
    double d;
};
printf("%zu\n", sizeof(union MyUnion)); // 输出 8,因为 double 是最大的成员

e. 指针

sizeof 返回的是指针本身所占用的字节数,而不是指针指向的内容的大小。

int a = 10;
int *p = &a;
printf("%zu\n", sizeof(p)); // 输出指针的大小 (8)
printf("%zu\n", sizeof(*p)); // 输出指针指向的内容的大小,即 sizeof(int) (4)

sizeof 的返回类型:size_t

sizeof 的返回类型是 size_t,这是一个在 <stddef.h> 中定义的无符号整数类型。

  • 为什么是无符号的? 因为大小不可能是负数。
  • 为什么是 size_t 而不是 int int 的大小可能不足以表示一个非常大的内存块(一个 4GB 的数组,sizeof 返回 4294967296,这在 32 位 int 上会溢出)。size_t 的大小足够大,可以表示平台上任何对象的大小。

特殊情况

a. sizeof("字符串字面量")

在 C 语言中,字符串字面量(如 "hello")会被存储在内存的只读数据区,并且编译器会自动在末尾添加一个 '\0' (空字符) 作为结束符。

sizeof("hello") 计算的是字符串内容加上结束符所占的总字节数。

printf("%zu\n", sizeof("hello")); // 输出 6 (h, e, l, l, o, \0)

b. sizeofvoid

sizeof(void) 在 C 标准中是无效的,会引发编译错误,因为 void 表示“无类型”,无法计算其大小。


总结表格

对象 sizeof 行为 示例 输出 (假设 64位系统, int=4)
基本类型 返回类型的大小 sizeof(int) 4
变量 返回变量类型的大小 int a; sizeof(a) 4
数组 返回整个数组的总大小 int arr[10]; sizeof(arr) 40
指针 返回指针本身的大小 int *p; sizeof(p) 8
结构体 返回结构体的总大小(考虑对齐) struct S {...}; sizeof(S) (根据对齐规则计算)
联合体 返回最大成员的大小 union U {...}; sizeof(U) (最大成员的大小)
字符串字面量 返回字符串长度 + 1 (含 \0) sizeof("hello") 6
函数参数中的数组 退化为指针,返回指针的大小 void f(int a[]) { sizeof(a); } 8

最佳实践

  1. 始终使用 size_t:存储 sizeof 的结果时,使用 size_t 类型。
    size_t size = sizeof(my_array);
  2. 对变量使用时加括号:虽然 sizeof var 是合法的,但 sizeof(var) 更清晰,并且在处理类似 sizeof(type) 的宏时能避免错误。
  3. 警惕数组参数退化:在函数中计算数组大小时,sizeof 不可靠,必须手动传递数组大小。
  4. 理解内存对齐:在处理结构体和内存布局时,sizeof 的结果可能会让你感到意外,理解对齐原理至关重要。
-- 展开阅读全文 --
头像
dede价格区间调用
« 上一篇 04-16
织梦后台数据库备份如何操作?
下一篇 » 04-16

相关文章

取消
微信二维码
支付宝二维码

目录[+]