深入解析sizeof
运算符:透视C语言中数组大小的细节
前言
当我们在编程中使用一维数组、二维数组和指针时 , 了解数组的大小和指针的操作非常重要
这篇博客将介绍了一些一维数组、二维数组和指针相关的内容
相信通过这篇文章你将会对sizeof
运算符在不同情景下的应用能有更深入的了解 , 以及指针与数组之间的关系
这些知识对于正确操作数组和指针非常重要
受于目前经验有限 , 如有错误和不足之处还望见谅、指正
本文所求字节数均是在32位环境下
先前知识简要介绍
sizeof
:
sizeof
是C语言中的一个运算符,用于获取数据类型或变量的大小(以字节为单位)
它的语法形式为sizeof(expression)
,其中expression
可以是数据类型、变量、数组、指针等
sizeof
运算符的结果是一个整数常量,表示给定表达式的大小。这个大小是在编译时确定的,并且与具体的机器和操作系统相关
数组名的意义:
sizeof
(数组名),这里的数组名表示整个数组,计算的是整个数组的大小- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(注意这里取出的是地址)
- 除此之外所有的数组名都表示首元素的地址
1.一维数组
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
-
printf("%d\n",sizeof(a)); //1
4
数组名单独放在
sizeof()
内部 , 数组名表示整个数组 , 计算的是整个数组的大小 -
printf("%d\n",sizeof(a+0)); //2
4
数组名没有单独放在
sizeof
内部 , 也没有& , 所以数组名a是数组首元素地址 ,a + 0
还是首元素地址 -
printf("%d\n",sizeof(*a)); //3
4
a并非单独放在
sizeof()
内部 , 也没有& , 所以数组名a是首元素的地址 ,*a
就是首元素 -
printf("%d\n",sizeof(a+1)); //4
4
a并非单独放在
sizeof
内部 , 也没有& , 所以数组名a是首元素地址 ,a + 1
就是第二个元素的地址 ,a + 1 == &a[1]
是第二个元素的地址 -
printf("%d\n",sizeof(a[1])); //5
4
a[1]
就是数组的第二个元素 , 这里计算的就是第二个元素的大小 -
printf("%d\n",sizeof(&a)); //6
4
取出的是数组的地址 , 但是数组的地址也是地址 , 是地址就是4个字节
数组的地址和数组首元素的地址的本质区别就是类型的区别 , 大小并没有区别
-
printf("%d\n",sizeof(*&a));
16
对数组指针解引用访问一个数组的大小
sizeof(*&a) --> sizeof(a)
-
printf("%d\n",sizeof(&a+1); //7
4
&a是数组的地址 ,
&a + 1
还是地址 , 是地址就是4个字节 -
printf("%d\n",sizeof(&a[0])); //8
4
&a[0]
是首元素的地址 , 计算的是地址的大小 -
printf("%d\n",sizeof(&a[0]+1));//9
4
&a[0]
是首元素的地址 ,&a[0] + 1
就是第二个元素的地址
2. 字符数组
第一种
//字符数组
char arr[] = {'a','b','c','d','e','f'};
//不是字符串, 系统不会自动在末尾添加一个null终止符
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
-
printf("%d\n", sizeof(arr)); //1
6
数组名arr单独放在
sizeof
内部 , 计算是整个数组的大小 -
printf("%d\n", sizeof(arr+0)); //2
4
数组首元素地址 , 是地址就是4/8个字节
指针变量的大小与类型无关 , 不管什么类型的指针变量 , 都是4/8个字节
-
printf("%d\n", sizeof(*arr)); //3
1
arr是首元素地址 ,
*arr
就是首元素 , 大小是一个字节 -
printf("%d\n", sizeof(arr[1])); //4
1
arr[1] == *(arr + 1)
-
printf("%d\n", sizeof(&arr)); //5
4
&arr是数组的地址 , 是地址就是4/8个字节
-
printf("%d\n", sizeof(&arr+1)); //6
4
&arr + 1是跳过数组后的地址 , 是地址就是4/8个字节
-
printf("%d\n", sizeof(&arr[0]+1)); //7
4
第二个元素的地址 , 是地址就是4/8个字节
-
printf("%d\n", strlen(arr)); //8
strlen
求字符串长度统计的是在字符串中\0之前出现的字符的个数
随机值 , arr是首元素的地址
-
printf("%d\n", strlen(arr+0)); //9
随机值
arr是首元素的地址 , arr + 0还是首元素的地址
-
printf("%d\n", strlen(*arr)); //10
ERR
arr是首元素的地址 , *arr就是首元素
站在
strlen
的角度 , 认为传参进去的是‘a’ - 97就是地址 , 97作为地址 , 直接进行访问 , 就是非法访问 -
printf("%d\n", strlen(arr[1])); //11
ERR
同样传入‘b’ - 98非法访问
-
printf("%d\n", strlen(&arr)); //12
随机值
在使用
strlen
函数时,传入的参数应该是一个以null结尾的字符串,而不是一个字符数组的地址在这种情况下,
strlen
函数会继续计算内存中的字符直到遇到null终止符。但是,由于arr是一个字符数组的地址,它可能不是以null结尾的字符串,所以strlen
函数会继续计算内存中的字符直到遇到null终止符,这可能导致访问到未初始化的内存,从而得到随机值 -
printf("%d\n", strlen(&arr+1)); //13
随机值
传入的数组地址的下一个地址 , 因为是地址, 而不是一个以\0结尾的字符
-
printf("%d\n", strlen(&arr[0]+1)); //14
随机值
传入的是第二个元素的地址
第二种
char arr[] = "abcdef";
//当你声明一个字符数组并用字符串初始化它时,编译器会自动在字符串的末尾添加一个null终止符
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
-
printf("%d\n", sizeof(arr)); //1
7
数组名单独放在
sizeof
内部 , 计算的是整个数组的大小, 其中还有终止符 -
printf("%d\n", sizeof(arr+0)); //2
4
不是数组名单独放在
sizeof
内部 , 数组首元素地址 -
printf("%d\n", sizeof(*arr)); //3
1
arr并非单独放在
sizeof
, arr是首元素地址 , *arr就是首元素 -
printf("%d\n", sizeof(arr[1])); //4
1
本质上是
arr[1] = *(arr + 1)
-
printf("%d\n", sizeof(&arr)); //5
4
&arr是数组的地址 , 只要是地址就是4个字节
-
printf("%d\n", sizeof(&arr+1)); //6
4
表示的是跳过该数组地址的下一个地址
-
printf("%d\n", sizeof(&arr[0]+1));//7
4
第二个元素的地址 , 是地址就是4个字节
-
printf("%d\n", strlen(arr)); //8
6
统计的的是\0之前的
-
printf("%d\n", strlen(arr+0)); //9
6
arr是首元素的地址 , arr + 0还是首元素的地址
-
printf("%d\n", strlen(*arr)); //10
ERR
arr是首元素的地址 , *arr就是首元素
站在
strlen
的角度 , 认为传参进去的是‘a’ - 97就是地址 , 97作为地址 , 直接进行访问 , 就是非法访问 -
printf("%d\n", strlen(arr[1])); //11
ERR
同样传入‘b’ - 98非法访问
-
printf("%d\n", strlen(&arr)); //12
随机值
在使用
strlen
函数时,传入的参数应该是一个以null结尾的字符串,而不是一个字符数组的地址 -
printf("%d\n", strlen(&arr+1)); //13
随机值
传入的是数组地址的下一个地址
-
printf("%d\n", strlen(&arr[0]+1)); //14
随机值
传入的是第二个元素的地址
3. 指向字符串常量的指针
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
-
printf("%d\n", sizeof(p)); //1
4
返回指针变量
p
的大小 , 计算的是指针变量的大小(指针变量的大小在32位环境下不管什么类型都是4个字节) -
printf("%d\n", sizeof(p+1)); //2
4
指针变量p指向的是常量字符串首元素 , p + 1指向的是常量字符串的第二个元素, p + 1还是地址 , 是4个字节
-
printf("%d\n", sizeof(*p)); //3
1
*p = 'a'
p指向该常量字符串的首地址 , 解引用是该常量字符串的首元素 , 是一个字节 -
printf("%d\n", sizeof(p[0])); //4
1
本质上
p[0] --> *(p + 0) --> *p =='a'
, 因此是该常量字符串的首元素 -
printf("%d\n", sizeof(&p)); //5
4
&p
取出的该字符串的地址 , 只要是地址就是4个字节 -
printf("%d\n", sizeof(&p+1)); //6
4
仍然是四个字节 , 只是跳过了整个该字符串 , 指向该字符串地址的下一个地址 , 只要是地址就是4个字节
-
printf("%d\n", sizeof(&p[0]+1)); //7
4
取出的是第一个字符的地址的下一个元素的地址 , 只要是地址就是4个字节
4. 二维数组
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
二维数组其实是一维数组的数组
-
printf("%d\n",sizeof(a)); //1
48
数组名单独放在了
sizeof
里面 , 表示的是整个数组(与整个数组的地址区分开) ,sizeof(a)
, 计算的是数组的大小 , 单位是字节 -
printf("%d\n",sizeof(a[0][0])); //2
4
a[0][0]
是数组的第一行第一个元素 , 这里计算的就是第一个元素的大小 , 单位是字节 -
printf("%d\n",sizeof(a[0])); //3
16
二维数组
a[0]
是第一行这个一维数组的数组名 , 数组名单独放在了sizeof
内部 ,a[0]
就表示整个第一行这个数组 ,sizeof(a[0])
计算的是整个第一行这个一维数组的大小 ,与一维数组的数组名单独放在
sizeof
里面类似只是 , 一维数组的数组名不是arr
而是a[0]
-
printf("%d\n",sizeof(a[0]+1)); //4
4
a[0]
并非单独放在sizeof
内部 , 也没有& , 所以a[0]
表示第一行这个一维数组首元素的地址, 也就是第一行第一个元素的地址与一维数组
arr
类似 ,arr
不是两种情况以外 , 全是表示的是首元素地址 -
printf("%d\n",sizeof(*(a[0]+1))); //5
4
a[0]+ 1
是第一行第二个元素的地址 ,*(a[0] + 1)
就是第一行第二个元素(与地址区分开) -
printf("%d\n",sizeof(a+1)); //6
4
a 作为二维数组的数组名 , 并没有单独放在
sizeof()
内部 , 也没有& , a 就是数组首元素地址(因为是二位数组的首元素 , 因此首元素是第一行) , 也就是第一行的地址(与第一行元素区分开 , 地址在32位下都是四个字节)在32位体系结构下,无论是char、int、short、long、float还是double类型的指针,它们的字节大小都是4个字节。
a 的类型是
int(*)[4]
(数组指针 , 因为是二维数组 , 因此指向的是一维数组 , 一维数组才是整形指针) ,a + 1就是第二行的地址 , 类型是:
int(*)[4]
-
printf("%zd\n", sizeof(*(a + 1))) //7
16
a + 1是第二行的地址 ,
*(a + 1)
就是第二行 , 计算的就是第二行的大小本质上
*(a + 1) -- a[1]
sizeof(a[1])
-a[1]
这个第二行的数组名 , 单独放在了sizeof
内部 , 计算的是第二行的大小 -
printf("%d\n",sizeof(&a[0]+1)); //8
4
a[0]
是第一行的数组名 ,&a[0]
取出的是数组的地址 , 取出的是第一行这个一维数组的地址 , 类型是int(*)[4]
&a[0] + 1
就是第二行的地址 , 类型是int(*)[4]
-
printf("%d\n",sizeof(*(&a[0]+1))); //9
16
*(&a[0] + 1)
得到的就是第二行 , 计算的就是第二行的大小 -
printf("%zd\n", sizeof(*a)); //10
16
a 表示数组首元素的地址 , 也就是第一行的地址 ,
*a
就是第一行 , 也就相当于是第一行的数组名本质上
*a --> *(a + 0) --> a[0]
-
printf("%zd\n", sizeof(a[3])); //11
16
其实并不会越界访问 ,
sizeof()
其实就是根据类型来算的 , 放入a[3]
其实和放入a[0]
没有任何区别 , 你在放入值的时候 ,sizeof()
就已经知道了你的类型