1. sizeof和strlen的对比
之前我们简单介绍了sizeof和strlen的一些区别,今天我们更深入讲解他们的不同。
1.1 sizeof
我们先来回顾一下sizeof的知识, sizeof 计算的是变量所占内存空间的大小,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。
sizeof不关心内存中存放的数据是什么,例如:
//sizeof计算变量
//size_t其实专门是设计给sizeof的,表示sizeof的返回值类型
int main()
{
int a = 0;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%u\n", sizeof(a));
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/364fa88197354b42a89f0f557e693cdc.png)
那strlen又是怎样的情况呢?
1.2 strlen
strlen是一个标准的C语言库函数,其在cplusplus中的原型如下:
![](https://img-blog.csdnimg.cn/ca2bf56e553f44f6ba4a377f92a7b446.png)
strlen函数统计的是从参数 str 这个地址开始向后,\0 之前字符串中字符的个数。所以strlen 函数会⼀直向后找 \0 字符,直到找到为止,所以可能存在越界访问的情况。
与sizeof相比,strlen是关心内存中存放的数据是什么,没有\0,它会疯的!
int main()
{
//char arr[] = "abcdef";
char arr[] = { 'a', 'b', 'c' };
printf("%d\n", strlen(arr));
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/7f9a754c60864fcb85d98485e803d2fd.png)
调试代码:
![](https://img-blog.csdnimg.cn/e3f7c92ffaeb49e98758aa2505513029.png)
通过调试出内存窗口,我们可以发现,要找到 ‘\0’,strlen要在字符 ‘c’ 还需要找12个字符!
总结:sizeof和strlen的细致对比
sizeof
1.sizeof是操作符;
2.sizeof计算操作数所占内存的大小,单位是字节;
3. sizeof不关注内存中存放的是什么数据。
strlen
1.strlen是库函数,使用时需要包含头文件 <string.h>;
2.srtlen是求字符串长度,统计的是 ‘\0’ 之前字符的个数;
3.strlen关注的是内存中是否有 ‘\0’,如果没有 ‘\0’,就会持续往后找,可能会导致越界。
讲解完以上内容,我们再来看一段有趣的代码:
int main()
{
short s = 10;//占2个字节
int i = 2;//占4个字节
int n = sizeof(s = i + 4);
printf("%d\n", n);
printf("%d\n", s);
运行结果:
![](https://img-blog.csdnimg.cn/8e40934cd7554345bd6419d963128703.png)
为什么会是上面这种结果呢?
别着急,我们慢慢来解释:
1.首先,sizeof 后面的表达式会发生截断,放到s中去,结果仍由s说了算 → 2个字节。
2.其次,sizeof 的操作数是一个表达式,表达式是不参与计算的,所以s仍为10。
为什么表达式不参与计算?
代码编译生成可执行程序,先编译再计算,编译过程执行sizeof,sizeof(s = i + 4) = sizeof(short),相当于跳过了s = i + 4。
通过上面这个例子我们可以得出结论:
表达式存在两个属性:1. 值属性; 2. 类型属性
2. 部分数组和指针笔试题解析
2.1 数组运算题解析
2.1.1 一维数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a[] = { 1, 2, 3, 4 };
printf("%d\n", sizeof(a));//数组a的大小
printf("%d\n", sizeof(a + 0));//数组名a并没有单独放在sizeof()内部,也没有&;
//所以a就是数组首元素的地址,是地址就是4/8个字节
printf("%d\n", sizeof(*a));//a就是首元素的地址, a = &a[0]
//*a就是第一个元素,也就是a[0],大小就是4个字节
printf("%d\n", sizeof(a + 1));//4或8—a就是数组首元素的地址(&a[0]—int*),a+1-->&a[1]
//a+1就是第二个元素的地址
printf("%d\n", sizeof(a[1]));//4—计算第二个元素的大小,单位是字节
printf("%d\n", sizeof(&a));//&a—取出的是数组的地址,但是数据的地址也是地址,地址的大小就是4/8个字节
printf("%d\n", sizeof(*&a));//第一种解读:相当于printf("%d\n", sizeof(a))—16
//第二种解读:
//&a—int (*p)[4]
//*p 访问一个数组的大小
//p+1 跳过一个数组的大小
printf("%d\n", sizeof(&a + 1));//跳过一个数组后的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&a[0]));//首元素的地址,4/8
printf("%d\n", sizeof(&a[0] + 1));//第二个元素的地址
//&a[1]
//&a[0] - int*
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/1a57443a81ad477e9d7a2000f18960cf.png)
2.1.2 字符数组
(一)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr + 0));//arr是数组首元素的地址,arr+0还是首元素的地址,地址大小就是4/8个字节
printf("%d\n", sizeof(*arr));//arr还是首元素的地址,*arr就是首元素,就占1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]就是数组的第二个元素,大小是1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,数组的地址也是地址,大小就是4/8个字节
printf("%d\n", sizeof(&arr + 1));//跳过一个数组后的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr[0] + 1));//第二个元素的地址
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/64233f5c87984088bc7659b7120d0ac5.png)
(二)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
printf("%d\n", strlen(arr));//随机值,因为数组中没有明确给出\0
printf("%d\n", strlen(arr + 0));//随机值
printf("%d\n", strlen(*arr));//*arr ——> 'a' ——> 97,非法访问
printf("%d\n", strlen(arr[1]));//非法访问
printf("%d\n", strlen(&arr));//随机值
//&arr const char*
//char (*p)[6] const char*
printf("%d\n", strlen(&arr + 1));//随机值(与前面的随机值差6)
printf("%d\n", strlen(&arr[0] + 1));//随机值
return 0;
}
(三)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
char arr[] = "abcdef";
//[a b c d e f \0]
printf("%d\n", sizeof(arr));//7
printf("%d\n", sizeof(arr+0));//arr是数组首元素的地址,arr+0还是首元素的地址,地址大小就是4/8个字节
printf("%d\n", sizeof(*arr));//arr表示首元素的地址,*arr就是首元素,大小是1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]就是数组第二个元素,大小也是1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,大小是4/8个字节
printf("%d\n", sizeof(&arr+1));//跳过1个数组后的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr[0]+1));//第二个元素的地址,大小是4/8个字节
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/69a78ada55574bcaa3bbceb8836ff0c7.png)
(四)
int main()
{
char arr[] = "abcdef";
//[a b c d e f \0]
printf("%d\n", strlen(arr));//6 arr是首元素的地址
printf("%d\n", strlen(arr+0));//6 arr + 0也是首元素的地址
printf("%d\n", strlen(*arr));//error - 非法访问
printf("%d\n", strlen(arr[1]));//error - 非法访问
printf("%d\n", strlen(&arr));//6 &arr是数组的地址,但是这个地址也是指向数组的起始位置
printf("%d\n", strlen(&arr+1));//跳过整个数组后的地址,从这里开始找\0,就是随机值
printf("%d\n", strlen(&arr[0]+1));//第二个元素的地址,长度是5
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/6d04a533f0e14a3db5314cd776346fe1.png)
(五)
int main()
{
char* p = "abcdef";
//[a b c d e f \0]
printf("%d\n", sizeof(p));//p是一个指针变量,大小是4/8个字节
printf("%d\n", sizeof(p+1));//第二个元素b的地址,大小是4/8个字节
printf("%d\n", sizeof(*p));//*p指向第一个元素a,大小是1个字节
printf("%d\n", sizeof(p[0]));//p[0] ——> *(p + 0),其实就是字符串中的首字符,大小是1个字节
//字符串可以看成是一个数组,p是数组首元素a的地址,p[0]其实就是访问数组中的元素a,大小是1个字节
printf("%d\n", sizeof(&p));//&p是p的地址,是地址大小就是4/8个字节
printf("%d\n", sizeof(&p+1));//&p + 1也是地址,&p + 1跳过p变量后的地址
printf("%d\n", sizeof(&p[0]+1));//4/8 - 第二个元素b的地址
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/9eff5c5ffc154b51831b24d48e155eff.png)
(六)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *p = "abcdef";
printf("%d\n", strlen(p));//6 - 字符串中有\0,从p中存放的是a的地址,从a的地址开始向后访问
printf("%d\n", strlen(p + 1));//5 - 从b的地址开始向后访问
printf("%d\n", strlen(*p));//error - 非法访问
printf("%d\n", strlen(p[0]));//error - 非法访问
printf("%d\n", strlen(&p));//随机值,&p是p的地址,从p所占空间的起始位置查找
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/efd4129d1223483e9f89300d2427715e.png)
2.1.2 二维数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <assert.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//计算的是整个二维数组的大小,单位是字节—48
printf("%d\n", sizeof(a[0][0]));//a[0][0]-第一个元素的大小,大小是4个字节
printf("%d\n", sizeof(a[0]));//16 - a[0]其实是第一行的数组名,这里第一行的数组名单独放在sizeof内部了,计算的是第一行的大小
printf("%d\n", sizeof(a[0] + 1));//
//a[0]是第一行这个数组的数组名,但是数组名并没有单独放在sizeof内部,所以数组名表示数组首元素的地址,也就是a[0][0]的地址,a[0] + 1是第一行第二个元素(a[0][1])的地址
printf("%d\n", sizeof(*(a[0] + 1)));//a[0] + 1是第一行第二个元素(a[0][1])的地址
//*(a[0] + 1)是第一行第二个元素,大小是4个字节
printf("%d\n", sizeof(a + 1));//a是二维数组的数组名,没有单独放在sizeof的内部,所以a就是数组首元素的地址,也就是第一行的地址
//a + 1就是第二行的地址
//a -- int (*)[4]
//a + 1 -- int (*)[4]
printf("%d\n", sizeof(*(a + 1)));//16 --> *(a + 1) == a[1]
printf("%d\n", sizeof(&a[0] + 1));//第二行的地址,是地址大小就是4/8个字节
//a[0]是第一行的数组名,&a[0]取出的是第一行的地址,&a[0] + 1得到的就是第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1)));//16
printf("%d\n", sizeof(*a));//16—数组名a就是数组首元素的地址,也就是第一行的地址,*a就是第一行的大小
//*a == *(a + 0) == a[0]
printf("%d\n", sizeof(a[3]));//16
//arr[3] == arr[0]
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/5d38ad486d784ed4ab259f49bb059030.png)
通过上述实例,我们可以先总结一波:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3.除了以上两种情况以外,其余所有的数组名都表示首元素的地址。
2.2 指针运算笔试题解析
(一)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/7bf51d3a39de4a979c5c1ebf09c3ff08.png)
代码分析:
![](https://img-blog.csdnimg.cn/f8da02d841cf4391b8bbe8c3a6f02413.png)
(二)
在X86环境下,假设结构体的大小是20个字节,下列程序输出的结果是啥?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
printf("%p\n", p + 0x1);//0x1 == 1
//0x100014 不是0x100020,需要转换成16进制数字
printf("%p\n", (unsigned long)p + 0x1);//p被强制类型转换成整型—0x100001
printf("%p\n", (unsigned int*)p + 0x1);//0x100004
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/df0c56aef9084947a6170f468b298494.png)
(三)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };//逗号表达式从左向右依次计算,但是整个表达式的结果是最后一个表达式的结果。
//1 3
//5 0
//0 0
int *p;
p = a[0];
printf("%d\n", p[0]);
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/13dacde4f5034aeca270dddb6341dc8d.png)
代码解析:
![](https://img-blog.csdnimg.cn/f9dba6a1081e44d2b4d7b146e04a0af1.png)
(四)重点!!!
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/6acd484e398f42c394eec86e8b9ee90c.png)
代码解析:
![](https://img-blog.csdnimg.cn/ede16ba2a9c2476e96918ecc838269c2.png)
p[4][2]可以看成是(p + 4)[2],两个指针相减得到的是两个之间的元素个数,即4。但是由于&p[4][2]的地址小于&a[4][2],所以结果为-4。
而-4在内存中是以补码的形式存放的:
1000 0000 0000 0000 0000 0000 0000 0100 — 原码
1111 1111 1111 1111 1111 1111 1111 1011 — 反码
1111 1111 1111 1111 1111 1111 1111 1100 — 补码
F F F F F F F C — 地址
(五)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/d048d4822969462c8c34477e5c468380.png)
代码解析:
![](https://img-blog.csdnimg.cn/0586d1ddb186407e813d2fa7300c6f17.png)
(六)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char *a[] = { "work", "at", "alibaba" };
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/6fb7c1cabe1a44e8b8b2925013c95466.png)
代码解析:
![](https://img-blog.csdnimg.cn/9d2dd31f5ade4ae087d6be2b4d22753b.png)
(七)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char* c[] = { "ENTER", "NEW", "POINT", "FIRST" };
char** cp[] = { c + 3, c + 2, c + 1, c };
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp + 3);
printf("%s\n", *cpp[-2] + 3);
printf("%s\n", cpp[-1][-1] + 1);
return 0;
}
运行结果:
![](https://img-blog.csdnimg.cn/0e0f21f87c8048bd98ad59dca74212a7.png)
代码解析:
![](https://img-blog.csdnimg.cn/25b16472f0644bbbb1b2c8c426cf2f20.png)
**++cpp
cpp指向 c + 3,++cpp后指向 c + 2,解引用两次后指向"POINT"。
*- -*++cpp + 3
cpp经过上述操作指向 c + 2,经过++cpp后,指向 c + 1,解引用再经过- -cpp,指向c,解引用两次后指向"ENTER",加3后指向"ER"。
*cpp[-2] + 3
cpp[-2]等价于 *(cpp - 2),刚才我们的cpp指向 c + 1,减2后指向 c + 3 ,解引用后指向c[3],再次解引用后指向"FIRST",加3后指向"ST"。
cpp[-1][-1] + 1
cpp[-1][-1]我们可以看作是*(*(cpp - 1) - 1),刚才cpp指向 c + 3,减1后指向 c + 2,解引用后指向c[2],减1之后指向c[1],c[1]解引用后指向"NEW",加1后指向"EW"。
好了,以上就是本期博客的全部内容了,这也是指针部分最后一期内容,看在我肝这么久的份上,还请小伙伴们多多支持。另外,由于工作原因,没办法做到像以前那样频繁更新,还请小伙伴们理解🤝,让我们一起在代码的世界里遨游。
![](https://img-blog.csdnimg.cn/25c0c4e9b12149509b971c89cc62c342.gif)