指针修仙之易混淆的sizeof和strlen


sizeof 和strlen

sizeof

第一次接触sizeof是在C语言基础学习中的操作符章节,我们知道:sizeof可以计算白能量所占内存空间的大小,单位十字街,如果操作舒适类型的话,激素那的是使用类型创建的变得所占空间的的大小

int main()
 {
	 int a = 10;
	 printf("%d\n", sizeof(a));
	 printf("%d\n", sizeof a);
	 printf("%d\n", sizeof(int));
	 return 0;
 }

strlen

strlen是C语言的库函数,功能是求字符串长度,函数原型如下

size_t strlen ( const char * str );

统计的是从strlen 函数的参数str 中这个地址开始向后,\0之前字符串中字符的个数。strlen 函数会⼀直向后找\0 字符,直到找到为⽌,所以可能存在越界查找。

比较

  • sizeof是一个操作符,而strlen是一个库函数
  • sizeof计算的是操作数的占内存空间的大小,单位是字节,strlen计算字符串的长度
  • sizeof不关注内存中的数据,strlen关注内存中是否存在\0

数组和指针妙题

一维数组

//一维数组
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));            //16
	//数据名表示数组首元素的地址,只有两种特殊情况,1是放在sizeof内部表示整个数组,2是&数组名表示整个数组的地址
	printf("%d\n", sizeof(a + 0));        //4/8
	//a表示首元素的地址 一个地址在内存中所占大小就是4/8
	printf("%d\n", sizeof(*a));           //4
	//a表示数组首元素地址,解引用表示首元素
	printf("%d\n", sizeof(a + 1));        //4/8
	//表示第二个元素的地址
	printf("%d\n", sizeof(a[1]));         //4
	//第一个元素
	printf("%d\n", sizeof(&a));           //4/8
	//&a表示取出了整个数组的地址,不过数组的地址也只是地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*&a));          //16
	//理解1:*&相互抵消,和第一句代码一样
	//理解2:&a是数组的地址,类型是:int(*)[4] 解引用访问的就是整个数组,切记元素类型决定了解引用时的权限大小
	printf("%d\n", sizeof(&a + 1));       //4/8
	//跳过整个数组
	printf("%d\n", sizeof(&a[0]));        //4/8
	printf("%d\n", sizeof(&a[0] + 1));    //4/8
	return 0;
}

字符数组

代码1

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));        //6
	//计算数组大小
	printf("%d\n", sizeof(arr + 0));    //4 || 8
	//第1个元素地址
	printf("%d\n", sizeof(*arr));       //1
	//首元素大小
	printf("%d\n", sizeof(arr[1]));     //1
	//首元素大小
	printf("%d\n", sizeof(&arr));       //4 || 8
	//数组地址
	printf("%d\n", sizeof(&arr + 1));   //4 || 8
	//跳过数组之后 的地址
	printf("%d\n", sizeof(&arr[0] + 1));//4 || 8
	//第二个元素的地址
	return 0;
}

代码2

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));        //随机值
	//数组中没有存储 \0 strlen函数会一直向后寻找
	printf("%d\n", strlen(arr + 0));    //随机值
	printf("%d\n", strlen(*arr));       //err 程序崩溃
	//解引用数组首元素地址,得到a-97 strlen函数将97看作地址访问 导致程序崩溃
	printf("%d\n", strlen(arr[1]));     //同上
	printf("%d\n", strlen(&arr));       //随机值
	//取数组的地址,数组地址就等于数组首元素地址,二者只是类型不同
	printf("%d\n", strlen(&arr + 1));   //随机值
	//跳过数组,向后找 \0
	printf("%d\n", strlen(&arr[0] + 1));//随机值
	//跳过数组第一个元素向后找 \0
	return 0;
}

代码3

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));         //7
	//字符串自带 \0
	printf("%d\n", sizeof(arr + 0));     //4/8
	//首元素地址
	printf("%d\n", sizeof(*arr));        //1
	//数组首元素
	printf("%d\n", sizeof(arr[1]));      //1
	//数组首元素
	printf("%d\n", sizeof(&arr));        //4/8
	//数组地址
	printf("%d\n", sizeof(&arr + 1));    //4/8
	//跳过数组后的地址
	printf("%d\n", sizeof(&arr[0] + 1)); //4/8
	//第二个元素的地址
	return 0;
}

代码4

int main()
{
	char arr[] = "abcdef";       
	printf("%d\n", strlen(arr));          //6
	//数组名是数组首元素地址,从数组首元素向后找\0
	printf("%d\n", strlen(arr + 0));      //6
	//printf("%d\n", strlen(*arr));         //崩溃
	//数组元素是数组首元素地址,解引用得到a-97 访问97 程序崩溃
	//printf("%d\n", strlen(arr[1]));       //崩溃
	//arr[1] <==> *(arr+1)
	printf("%d\n", strlen(&arr));         //6
	//数组地址等于数组首元素地址,二者只是类型不同
	printf("%d\n", strlen(&arr + 1));     //随机值
	//跳过数组向后找\0
	printf("%d\n", strlen(&arr[0] + 1));  //5
	//从第二个元素向后找\0
	return 0;
}

代码5

int main()
{
	char* p = "abcdef";                //常量字符串           
	printf("%d\n", sizeof(p));         //4/8
	//p是指针变量,存地址
	printf("%d\n", sizeof(p + 1));     //4/8
	//第二个元素的地址 因为p的类型是char* 所以+1也之跳过一个char*
	printf("%d\n", sizeof(*p));        //1
	//p的类型是char* 所以一次只能访问一个字节
	printf("%d\n", sizeof(p[0]));      //1
	//常量字符串中的第一个元素
	printf("%d\n", sizeof(&p));        //4/8
	//p的地址 是一个二级指针 char**
	printf("%d\n", sizeof(&p + 1));    //4/8
	//指向一个地址
	printf("%d\n", sizeof(&p[0] + 1)); //4/8
	//b的地址
	return 0;
}

代码6

int main()
{
	char* p = "abcdef";
	printf("%d\n", strlen(p));         //6
	//常量字符串的长度
	printf("%d\n", strlen(p + 1));     //5
	//从第二个字符开始向后数
	//printf("%d\n", strlen(*p));        //崩溃
	//printf("%d\n", strlen(p[0]));      //崩溃
	printf("%d\n", strlen(&p));        //随机值
	printf("%d\n", strlen(&p + 1));    //随机值
	printf("%d\n", strlen(&p[0] + 1)); //5
	return 0;
}

二维数组

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));            //48
	//二维数组的大小  
	printf("%d\n", sizeof(a[0][0]));      //4
	//第一行第一列的元素  
	printf("%d\n", sizeof(a[0]));         //16
	//a[0]单独放在sizeof内部表示是第一行的数组名,计算的是第一行数组元素的大小
	printf("%d\n", sizeof(a[0] + 1));     //4/8
	//这里的a[0]表示的是第一行第一个元素的地址 a[0][0],在加一得到a[0][1]的地址
	printf("%d\n", sizeof(*(a[0] + 1)));  //4
	//第一行第二个元素
	printf("%d\n", sizeof(a + 1));        //4/8
	//第二行元素的地址
	printf("%d\n", sizeof(*(a + 1)));     //16
	//数组名表示数组首元素地址也就是第一行地址,+1表示第二行地址,再*表示第二行元素
	//a+1是第二行元素的地址,类型是 int(*)[4],指针数组,解引访问的是十六个字节
	printf("%d\n", sizeof(&a[0] + 1));    //4/8
	//第二行地址
	printf("%d\n", sizeof(*(&a[0] + 1))); //16
	//第二行元素
	printf("%d\n", sizeof(*a));           //16
	//数组第一行元素
	printf("%d\n", sizeof(a[3]));         //16
	//越界访问,但是sizeof内部的表达式不会计算,所以不会报错
	return 0;
}

指针运算妙题

代码1

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1); 
	printf("%d,%d", *(a + 1), *(ptr - 1)); // 2 5
	return 0;
}

心得: 类型决定了解引用的权限

代码2

//x86环境下
//假设结构体大小是20个字节
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);                //0x00100014
	 //p是结构体指针 +1跳过一个结构体
	 printf("%p\n", (unsigned long)p + 0x1); //0x00100001
	 //强制类型转换成无符号整形 +1就是+1
	 printf("%p\n", (unsigned int*)p + 0x1); //0x00100004
	 //强制转换成无符号整型指针 +1 跳过四个字节
	 return 0;
 }

代码3

int main()
 {
	 int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	 int *p;
	 p = a[0];
	 printf( "%d", p[0]);
	 return 0;
 }
  • 用逗号表达式初始化数组

代码4

//假设环境是x86环境 
#include <stdio.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]); // 0XFFFFFFFC, -4
	 return 0;
 }
  • 先创建一个二维数组,五行五列
  • 创建一个数组指针,指向的数组存四个元素
  • 将a的地址赋值给p,a是数组名,是数组首元素的地址
  • p[4][2] <==> *(*(p+4)+2) p+4跳过十六个整形,解引用后再+2,跳过两个整形
  • 以%p来打印,是看作无符号数打印,-4的补码直接看作地址来打印 所以结果为FFFFFFFC, -4

代码5

int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 
	int* ptr1 = (int*)(&aa + 1);  // 跳过二维数组,将类型强制转换为int*
	int* ptr2 = (int*)(*(aa + 1));//跳过第一行,解引用,指向第二行的第一个元素,将类型强制转换为int*
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); //10 ,5
	return 0;
}

代码6

int main()
{
	char* a[] = { "work","at","alibaba" }; // 创建一个指针数组,数组中元素类型为char* 常量字符串
	char** pa = a; // 数组名表示数组首元素地址,将数组首元素地址赋值给pa
	pa++; // 跳过数组第一个元素
	printf("%s\n", *pa); // at
	return 0;
}

代码7

int main()
 {
	 char *c[] = {"ENTER","NEW","POINT","FIRST"};
	 char**cp[] = {c+3,c+2,c+1,c};
	 char***cpp = cp;
	 printf("%s\n", **++cpp);       //POINT
	 printf("%s\n", *--*++cpp+3);   //ER
	 printf("%s\n", *cpp[-2]+3);    //ST
	 printf("%s\n", cpp[-1][-1]+1); //EW
	 return 0;
 }

在这里插入图片描述

  • 先画图看内存中的结构
  • 需要注意的就是++会改变指针的指向
  • []不影响指针的指向

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值