前言
之前总是误认为指针变量的大小和指针所指向的对象有关系,搞网络驱动时,使用kmalloc做内存申请时发现了一些端倪,先简单介绍下sizeof:
- sizeof 是一个关键字,它是一个编译时的运算符,用于判断变量或数据类型的字节大小。
- sizeof 运算符可用于获取类、结构、共用体和其他用户自定义数据类型的大小。
sizeof有两种语法形式,如下:
1、sizeof(type_name);//sizeof(类型);
2、sizeof object;//sizeof对象;
下面先来看一段代码:
#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[]){
char str1[] = "hello";
char str2[100] = "hello";
int arr1[] = {3,1,4};
char *ptr1 = "hello";
const char *ptr2 = str1;
int *ptr3 = arr1;
printf("size of type : %ld,%ld,%ld,%ld\n",sizeof(char),sizeof(int),sizeof(size_t),sizeof(long));
printf("size of type*: %ld,%ld,%ld,%ld\n",sizeof(char*),sizeof(int*),sizeof(size_t*),sizeof(long*));
printf("size of arr_var: %ld,%ld,%ld\n",sizeof(str1),sizeof(str2),sizeof(arr1));
printf("size of ptr_var: %ld,%ld,%ld\n",sizeof(ptr1),sizeof(ptr2),sizeof(ptr3));
return 0;
}
运行结果:
1 数据类型的sizeof
sizeof(type_name)
很简单,64位操作系统中char
占1个字符,int
占4个字符、size_t(unsigned long int)、long
都是8个字符。
2 数据对象的sizeof
2.1 指针变量的sizeof
printf("size of type* :%ld, %ld, %ld, %ld\n", sizeof(char*), sizeof(int*), sizeof(size_t*), sizeof(void*)) ;
从输出中可以看出,无论指针类型是char*、int*、size_t*
还是void*
,大小均是输出全是8bytes。
通常,在32位计算机中,一个指针变量的返回值通常是4(bytes),在64位系统中指针变量的sizeof通常为8(bytes)。
指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,之前做C++时,MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)
2.2 数组的sizeof
char str1[] = "hello";
char str2[100] = "hello";
int arr1[] = {3,1,4};
printf("size of arr_var: %ld,%ld,%ld\n",sizeof(str1),sizeof(str2),sizeof(arr1));
str1是变长’字符串’,等价于 “hello\0”,加上字符末尾的NULL终止符,内存一共分配6个字节;
sizeof(str2) 的值是 100, 等价于 sizeof(char [100]) ,在内存中一共分配 1*100 个字节;
sizeof(arr1)的值是12,等价于sizeof(int[3]),每个int占4字节,三个共分配12字节。
很好理解,分配了多少bytes内存,sizeof就是多大。
2.3 数组名和指针的关系
这里这里不得不提的是,数组和指针的关系,因为:
我们经常将数组名和指针交换使用,把指针当成数组,或者把数组名赋给指针,通过指针来访问数组成员。有些资料上说,“数组名本身就是一个指针,是一个常量指针,即arr等价于int* const arr,因此不能试图修改数组名的值”,甚至有些直接说“数组名就是指针”。
我的理解是这句话对了一半吧,数组名在作为左值时,只是一个符号,并不指向保存地址,但作为右值(函数形参)使用时,就可以当成指针使用。
看下面的代码:
char str1[] = "hello";
char str2[100] = "hello";
int arr1[] = {3,1,4};
char *ptr1 = "hello";
const char *ptr2 = str1;
int *ptr3 = arr1;
printf("size of arr_var: %ld,%ld,%ld\n",sizeof(str1),sizeof(str2),sizeof(arr1));
printf("size of ptr_var: %ld,%ld,%ld\n",sizeof(ptr1),sizeof(ptr2),sizeof(ptr3));
执行结果:
可以看出数组的sizeof大小和指向该数组指针的sizeof的区别,数组的sizeof就是该数组分配的内存大小;而指针的sizeof和指向的对象并没有关系,由CPU的寻址位数决定。
刚刚说当数组作为右值或者函数变量时,可以作为指针,下面代码可以来测试下:
void func1(char a1[3])
{
size_t c1=sizeof(a1);
printf("%ld\n",c1);
}
void func2(char a2[])
{
size_t c2=sizeof(a2);
printf("%ld\n",c2);
}
调用func1,func2,输出结果如下:
此时,函数参数a1已不再是数组类型,而是蜕变成指针,所以sizeof(a1)的值是8,而不是6,按址传参。
2.4结构体的sizeof
typedef struct {
char c;
int i;
} S1;
可能有些初学的朋友,看到S1应该认为,char占1个字节,int占4个字节,所以sizeof(S1)=5;我们运行下就会发现sizeof(S1)=8;
结构体的sizeof也有点复杂,主要和“字节对齐”有关,编译器默认会对结构体进行处理,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,这样是为了加快计算机的处理速度,减少指令周期。
当然也通过强制对齐的方式使结构体字节对齐。我经常用以下两种:
#pragma pack (1) //pragma pack(n)按照n字节对齐
typedef struct {
char c;
int i;
}S2;
#pragma pack() //取消自定义字节对齐
//attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐
typedef struct {
char c;
int i;
}__attribute__((packed))S3;
printf("sizeof(S1) = %ld\nsizeof(S2) = %ld\nsizeof(S3) = %ld\n",sizeof(S1),sizeof(S2),sizeof(S3));
这里对结构题的sizeof指示简单介绍,没有深入探讨,有兴趣的小伙伴可以自行学习下。
2.5联合体的sizeof
联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值的对齐结果。
typedef union {
int i;
char c;
int arr[2];
} U;
在联合体U中sizeof(arr)=8最大,即sizeof(U)=8。
3对sizeof的一点理解
sizeof()
是 返回一个类型的在内存中说占的字节数。 属于运算符 ,同为 运算符的还有 + 、-、*、/ 、%
等。而这个是在编译时候就被执行,而非运行时候执行,一开始就已经是计算机被知道的。
C标准和实际上的编译器一开始就定义好了,在32位电脑中,int 占据 4 字节, char占据1字节;
也有人定义如下两个宏,对sizeof()进行代码的实现;但注意的是,这是网友使用宏来实现的,而非编译器正在的实现方式,只是对会我们理解sizeof()
有所帮助;
//适用于非数组
#define _sizeof(T) ((size_t)((T*)0 + 1))
//适用于数组
#define array_sizeof(T) ((size_t)(&T+1)-(size_t)(&T))
这部分理解是学习了好些博客和书籍的一点想法,大家学习时也可以多看看其他人的理解。
实际上对sizeof的理解,如果能深入到编译器,看看编译器的代码将一目了然,我这里只是从现象分析了一下sizeof,感兴趣的同学可以深究一下,我只是看了一下代码,就不在此卖弄了。