目录
1. sizeof和strlen的对⽐
1.1 sizeof
在学习操作符的时候,我们学习了sizeof ,sizeof 计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
#inculde <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
return 0;
}
1.2 strlen
strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
size_t strlen ( const char * str );
统计的是从 strlen 函数的参数 str 中这个地址开始向后,\0之前字符串中字符的个数。
strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。
> #include <stdio.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
return 0;
}
1.3 sizeof 和strlen的对⽐
sizeof
- sizeof是操作符
- sizeof计算操作数所占内存的⼤⼩,单位是字节
- 不关注内存中存放什么数据
strlen
- strlen是库函数,使⽤需要包含头⽂件 string.h
- srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数
- 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界
2. 数组和指针笔试题解析★★★★★
2.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));
解析:
- sizeof ( a ) , a是数组名,计算的是整个数组的大小,数组的类型是 int ,数组中有4个元素,每个元素(类型 int )占4个字节。所以是 4 * 4=16
- sizeof ( a + 0 ) ,首先要考虑 a+0 是什么?这个问题很重要。我们知道 sizeof ( 数组名 )表示的是整个数组,但是这里是 a+0 ,所以就不是数组名,a 表示的是首元素的地址,a+0 也就是首元素的地址(等价与 & a [0] ),所以sizeof ( a + 0)计算的是地址的大小。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof ( * a ) , a 没有单独放在 sizeof 内部,所以 a 是首元素的地址,* 是解引用,* a 表示的是首元素,一个字节,所以是 4
- sizeof ( a + 1 ) , a是首元素的地址( & a [ 0 ]------int* ) , +1—> & a[1] , 也就是第二个元素的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof ( a [1] ) , 表示的是计算第二个元素的大小,所以是4个字节。
- sizeof ( &a ) , &a 表示的是整个数组的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof ( * &a ) , &a 表示的是整个数组的地址,解引用一下表示的是整个数组,数组的类型是 int ,数组中有4个元素,每个元素(类型 int )占4个字节。所以是 4 * 4=16
- sizeof ( &a+1 ) ,&a=int ( *p ) [4] , *p 访问的是一个数组的大小, p+1 跳过一个数组的大小。所以 &a+1 应该是跳过整个数组,访问的是4后面的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof( &a[0] ) ,&a[0] 表示的是首元素的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof( &a[0]+1 ) ,&a[0]+1==&a[1] ,表示的是第二个元素的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
2.2 字符数组
代码一:
char arr[] = {‘a’,‘b’,‘c’,‘d’,‘e’,‘f’};
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));
解析:
- sizeof ( arr ) , 表示的是整个数组的大小,是 char 类型,每个元素占1个字节。所以 1 * 6=6
- sizeof ( arr+0 ) , 首先要考虑 arr+0 是什么?这个问题很重要。我们知道 sizeof ( 数组名 )表示的是整个数组,但是这里是 arr+0 ,所以就不是数组名,arr 表示的是首元素的地址,arr+0 也就是首元素的地址(等价与 & a [0] ),所以sizeof ( a + 0)计算的是地址的大小。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof ( * arr ) , a 没有单独放在 sizeof 内部,所以 a 是首元素的地址,* 是解引用,* a 表示的是首元素,一个字节,所以是 1
- sizeof ( arr[1] ) , 表示的是计算第二个元素的大小,所以是1个字节。
- sizeof ( &arr ) , &arr 表示的是整个数组的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof ( &arr+1 ) , &arr+1 应该是跳过整个数组,访问的是 f 后面的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof ( &arr[0]+1) , &arr[0]+1==&arr[1] ,表示的是第二个元素的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
代码二:
char arr[] = {'a','b','c','d','e','f'};
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));
size_t strlen(const char* )
解析:
- strlen (arr) , 随机值。因为 strlen 是遇到 \0 后停止,无法确定 \0 的位置,所以无法确定。
- strlen(arr+0) , 随机值。arr 表示的是首元素的地址,arr+0 也就是首元素的地址(等价与 & a [0] ),因为 strlen 是遇到 \0 后停止,无法确定 \0 的位置,所以无法确定。
- strlen( * arr) , 非法访问。arr 是首元素的地址,*arr 是 ‘a’ , a的ASCII编码是97,所以就会把97当成地址来访问。(理解strlen的参数)
- strlen(arr[1]) , 非法访问。arr 是首元素的地址,arr[1]==‘b’ , b的ASCII编码是98,所以就会把98当成地址来访问。(理解strlen的参数)
- strlen(&arr) , 随机值。&arr是整个数组的地址,也就是首元素的地址。&arr的类型是 char (*p)[6] 数组指针类型,但是strlen的参数类型是const char *类型,这里编译器就会自动转化,所以就不用考虑类型不匹配问题。因为 strlen 是遇到 \0 后停止,无法确定 \0 的位置,所以无法确定。
- strlen(&arr+1) , 随机值。&arr是整个数组的地址,也就是首元素的地址。&arr+1表示的是跳过整个数组,指针指向f后面的位置。因为 strlen 是遇到 \0 后停止,无法确定 \0 的位置,所以无法确定。
- strlen(&arr[0]+1) , 随机值。&arr[0]表示的是数组首元素的地址,&arr[0]+1就是b的地址,因为 strlen 是遇到 \0 后停止,无法确定 \0 的位置,所以无法确定。
代码三:
> char arr[] = "abcdef";
printf("%llu\n", sizeof(arr));
printf("%llu\n", sizeof(arr+0));
printf("%llu\n", sizeof(*arr));
printf("%llu\n", sizeof(arr[1]));
printf("%llu\n", sizeof(&arr));
printf("%llu\n", sizeof(&arr+1));
printf("%llu\n", sizeof(&arr[0]+1));
解析:
- sizeof(arr) , 7。sizeof不关心里面的内容,只在乎占了多大的空间。sizeof(arr)表示的是整个数组的大小,是 char 类型,每个元素占1个字节。所以 1 * 7=7
- sizeof(arr+0) ,4/8。 首先要考虑 a+0 是什么?这个问题很重要。我们知道 sizeof ( 数组名 )表示的是整个数组,但是这里是 a+0 ,所以就不是数组名,a 表示的是首元素的地址,a+0 也就是首元素的地址(等价与 & a [0] ),所以sizeof ( a + 0)计算的是地址的大小。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(arr) ,1。 a 没有单独放在 sizeof 内部,所以 a 是首元素的地址, 是解引用,* a 表示的是首元素,一个字节,所以是 1
- sizeof(arr[1]), 表示的是计算第二个元素的大小,所以是1个字节。
- sizeof(&arr), &arr 表示的是整个数组的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(&arr+1), &arr+1 应该是跳过整个数组,访问的是 f 后面的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(&arr[0]+1), &arr[0]+1==&arr[1] ,表示的是第二个元素的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
代码四:
char arr[] = "abcdef";
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));
解释:
- strlen(arr), 6。因为 strlen 是遇到 \0 后停止,所以就是6。
- strlen(arr+0),6。arr 表示的是首元素的地址,arr+0 也就是首元素的地址(等价与 & a [0] ),因为 strlen 是遇到 \0 后停止,所以就是6。
- strlen(*arr), 非法访问。arr 是首元素的地址,*arr就是’a’ , a的ASCII编码是97,所以就会把97当成地址来访问。(理解strlen的参数)
- strlen(arr[1]), 非法访问。arr 是首元素的地址,arr[1]==‘b’ , b的ASCII编码是98,所以就会把98当成地址来访问。(理解strlen的参数)
- strlen(&arr),&arr是整个数组的地址,也就是首元素的地址。&arr的类型是 char (*p)[6] 数组指针类型,但是strlen的参数类型是const char *类型,这里编译器就会自动转化,所以就不用考虑类型不匹配问题。因为 strlen 是遇到 \0 后停止,所以就是6。
- strlen(&arr+1),随机值。&arr是整个数组的地址,也就是首元素的地址。&arr+1表示的是跳过整个数组,指针指向 \0 后面的位置。因为 strlen 是遇到 \0 后停止,无法确定 \0 的位置,所以无法确定。
- strlen(&arr[0]+1),5。 &arr[0]表示的是数组首元素的地址,&arr[0]+1就是b的地址,因为 strlen 是遇到 \0 后停止,所以就是5。
代码五:
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));
解释:
注意:
*p表示的是a(首元素),
p表示的是a(首元素)的地址
- sizeof( p),p表示的是a(首元素)的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(p+1),p+1表示的是b的地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(*p),*p表示的是a(首元素),所以是1字节
- sizeof(p[0]),p[0]表示的是a(首元素),所以是1字节
- sizeof(&p),&p表示的是P的地址,(注意:p本身也是有地址,p存储空间里存放的是a的地址),,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(&p+1),&p表示的是P的地址,(&p+1)表示的是跳过一个P的地址,指针指向下一个地址。是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(&p[0]+1),&p[0]表示的是a的地址,&p[0]+1表示的是b的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
代码六:
char *p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
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));
解释:
注意:
*p表示的是a(首元素),
p表示的是a(首元素)的地址
- strlen( p),p表示的是a(首元素)的地址,因为 strlen 是遇到 \0 后停止,所以就是6。
- strlen(p+1),p表示的是a(首元素)的地址,p+1表示的是b的地址,因为 strlen 是遇到 \0 后停止,所以就是5。
- strlen(*p), 非法访问。*p表示的是a(首元素), a的ASCII编码是97,所以就会把97当成地址来访问。(理解strlen的参数)
- strlen(p[0]), 非法访问。p[0]表示的是a(首元素),a的ASCII编码是97,所以就会把97当成地址来访问。(理解strlen的参数)
- strlen(&p),随机值。&p表示的是P的地址,(注意:p本身也是有地址,p存储空间里存放的是a的地址),因为 strlen 是遇到 \0 后停止,所以无法确定。
- strlen(&p[0]+1),5。&p[0]表示的是a的地址,&p[0]+1表示的是b的地址,因为 strlen 是遇到 \0 后停止,所以5字节。
2.3 ⼆维数组
> 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]));
解释:
- sizeof(a),计算的是整个二维数组的大小,3* 4* 4 =48,单位字节
- sizeof(a[0][0]),计算的是第一行第一个元素,4个字节
- sizeof(a[0]),a[0]表示的是第一行的数组名,数组名单独放在sizeof里面,计算的是第一行的大小,4* 4=16,单位字节
- sizeof(a[0]+1),a[0]没有单独放在sizeof里面,所以这里表示的是第一行第一个元素的地址,所以a[0]+1表示的是第一行第二个元素的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(*(a[0]+1)),a[0]没有单独放在sizeof里面,所以这里表示的是第一行第一个元素的地址,所以a[0]+1表示的是第一行第二个元素的地址,解引用,*(a[0]+1)表示的是表示的是第一行第二个元素,4个字节
- sizeof(a+1),a 没有单独放在sizeof里面,也没有&,所以这里a表示的是数组首元素的地址,也就是第一行的地址。a+1表示的是第二行的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(*(a+1)),(a+1)表示的是第二行的地址,*(a+1)==a[1] 表示的是第二行的数组名,所以数组名单独放在sizeof里面,计算的是第二行的大小,4 * 4=16
- sizeof(&a[0]+1),a[0]表示的是第一行的数组名,&a[0]表示的是第一行的地址,&a[0]+1表示的是第二行的地址,是地址就是4个或8个字节。(x86 4个字节,x64 8个字节)
- sizeof(*(&a[0]+1)),&a[0]+1表示的是第二行的地址, * (&a[0]+1)表示的是第二行,计算的是第二行的大小,4 * 4=16
- sizeof(*a),a表示的是数组首元素的地址,也就是第一行的地址。*a表示的是第一行,计算的是第一行的大小,4 * 4=16
- sizeof(a[3]),这里不是报错,也不是越界访问,编译器会把a[3]看成是数组名,相当于arr[0],所以就是16
数组名的意义:
- sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
- &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表⽰⾸元素的地址。
3. 指针运算笔试题解析★★★★★
3.1 题⽬1:
#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;
}
程序的结果是什么?
解析:
- *(a + 1),a表示的是数组的首元素的地址,a+1就是 2 的地址,所以 * (a + 1)解引用表示的是2
- *(ptr - 1), &a表示的是整个数组的地址,&a + 1表示的是指针指向整个数组的后面,也就是5的后面的地址。(int *)(&a + 1)这里进行了int *的强制类型转化, 所以 *ptr就是表示5后面的内存空间。ptr - 1就是5的地址, * (ptr - 1)就是5。
3.2 题⽬2
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);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
在X86环境下,假设结构体的⼤⼩是20个字节,程序输出的结构是啥?
解析:
分析:p是指针变量,不是类型,地址是0x100000
- p + 0x1,p是指针变量,不是类型,所以p+1就是跳过一个结构体,结构体的⼤⼩是20个字节,地址是0x100000,加上20就变成0x100014
- (unsigned long)p + 0x1,p是指针变量,(unsigned long)p强制转换成long类型,类型加1,就是加1,所以结果是0x100001
- (unsigned int * )p + 0x1,p是指针变量,(unsigned int*)p表示的是强制转换成int* 型,指针类型+1表示的是跳过一个指针类型,所以结果就是0x100004
3.3 题⽬3
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
解析:
分析:int a[3][2] = { (0, 1), (2, 3), (4, 5) };这是什么?
这是逗号表达式,从左向右计算,从整体上最后的结果为右边的表达式
所以 int a[3][2] ={1,3,5}
- p[0],p = a[0]=&a[0][0],p[0]==a[0][0],所以就是1
3.4 题⽬4
#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]);
return 0;
}
假设环境是x86环境,程序输出的结果是啥?
解析:
- &p[4][2] - &a[4][2],如下图所示,地址-地址=中间的元数个数=-4
【-4】原:10000000 00000000 00000000 00000100
【-4】反:111111111 111111111 111111111 111111011
【-4】补:111111111 111111111 111111111 111111100
【-4】16进制:F F F F F F F C
因为输出格式%p,所以就是FFFFFFFC - &p[4][2] - &a[4][2],输出格式%d,所以就是-4
3.5 题⽬5
#include <stdio.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;
}
解析:
- *(ptr1 - 1),&aa取出的是整个数组的地址,&aa+1就是跳过整个数组,指针指向整个数组的后面,(int *)(&aa+1)进行类型的强转赋给 *ptr1,所以ptr1存储的就是&aa+1的地址,ptr1 - 1表示的就是10的地址, * (ptr1 - 1)就是10。
- (ptr2 - 1),aa表示的是首元素的地址,也就是第一行的地址,aa + 1就是跳过第一行,指针指向第二行的地址, * (aa + 1)就是6,(int * )((aa + 1)进行类型的强转,ptr2就是6的地址,ptr2 - 1就是5的地址,所以 *(ptr2 - 1)就是5
3.6 题⽬6
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
解释:
char *a[] = {“work”,“at”,“alibaba”};
注意:并不是把字符串放到数组中去,而是把字符串的首字母放到数组中去。
所以 a是第一行的地址,放到了pa中, pa++,指针也就指向了第二行
所以 *pa就是at
3.7 题⽬7
#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;
}
解析:
char *c[] = {“ENTER”,“NEW”,“POINT”,“FIRST”};
char**cp[] = {c+3,c+2,c+1,c};
char ***cpp = cp;
如图所示:
- ** ++cpp,cpp指向的是 cp中F的地址,++cp指针指向**cp 中p,*cp是p,cp指针指向c中的p,所以cp就是POINT
- ** –++cpp+3 **,根据上一步可知,cpp指向的是cp中p的地址,++cpp指向的是 * * cp中N(c+1)的地址,*++cpp就是c中的N,-- *++cpp指向c中的E, ** * – *++cpp就是ENTER,但是这时候指针指向的是E,+3就变成指向T后面的E,所以结果就是打印ER
- *cpp[-2]+3,根据上一步可知,cpp指向的是 cp中N的地址,*cpp[-2]= **(cpp-2),所以cpp指向的是 * * cp中F(c+3)的地址,*cpp[-2]就表示c中的F,+3指针就指向S,所以打印的结果就是ST
- cpp[-1][-1]+1,cpp[-1][-1]+1=* ( *(cpp-1)-1)+1,注意:cpp[-2]不会改变指针,根据上上一步可知,cpp指向的是 cp中N的地址,cpp-1指向cp中p的地址, * (cpp-1)指针指向c中的N的地址, * ( *(cpp-1)-1)就是c中的NEW,+1指针冲N指向E,所以打印结果就是EW
文章到这就结束了~~