当前位置: 首页 > news >正文

C语言第八章指针五

一.sizeof和strlen的区别

1.sizeof操作符介绍

        在C语言中,sizeof是进行计算变量大小的运算符,用sizeof运算符时,可以将未知变量大小的变量名放在sizeof后面的括号内即可。这样就可以求出该变量在内存中所占据的空间大小(单位是字节)。当操作数不是变量,而是数据类型的话,则计算的是创建一个该数据类型的变量,在内存中所占据的空间大小(单位是字节)。这里需要注意的是sizeof只关注操作数占用内存的大小,而不会关注内存中存放了什么数据,并且当操作数是表达式时,也不会对表达式进行计算。

#include <stdio.h>
int main()
{int a = 10;printf("%zd\n", sizeof(a));printf("%zd\n", sizeof a);printf("%zd\n", sizeof(int));return 0;
}

        比如上方的代码:首先创建了一个整型变量a,用于存放整型值10。在下面的三行代码中,分别对变量a和数据类型int进行运算,从而得到该变量(该种数据结构)在内存中所占据空间的大小。因为都是整型变量,所以输出结果为:4        4        4。

        在上述代码中,其实三行代码就是sizeof运用时的三种格式:第一种:sizeof(表达式);第二种:sizeof 表达式;第三种:sizeof(类型名)。在这三种写法中,第一种和第二种是等效的,均计算的是a变量的大小,而第三种计算的是创建一个该类型的变量,所占空间的大小。这里需要注意的是:当操作数是表达式时,括号是可有可无的;但是当操作数为数据类型时,括号是必须带的。

        在C语言使用sizeof操作符时,建议占位符使用%zd,这样编译器不会报警告(虽然代码依然可以运行)。原因是sizeof操作符的返回值是无符号整型(size_t类型),所以占位符建议用%zd。

2.strlen函数介绍

        在C语言中,strlen是其中的库函数,作用是:求出字符串的长度(不包括字符串末尾的 \0)。下面是strlen函数的具体解释:

        根据上述图片解释:strlen函数的返回值类型是size_t类型(无符号整型),函数参数为字符指针str。strlen函数的具体作用是:从str开始往后找,直到找到 \0,统计在这之前的字符个数。

        但是这里要注意的是:strlen函数有可能会越界访问,如果系统开辟地址中的内容没有 \0,则会一直向后查找,一直找到 \0为止。

        下面给出strlen函数的模拟实现和具体使用:

size_t my_strlen(const char *str) 
{size_t length = 0;while (*str != '\0'){length++;str++;}return length;
}
#include <stdio.h>
int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));printf("%d\n", strlen(arr2));printf("%d\n", sizeof(arr1));printf("%d\n", sizeof(arr2));return 0;
}

        上述两个代码块中,分别是strlen函数的模拟实现和具体使用细节。

        在第一个函数模拟实现的代码块中,函数会通过解引用str(函数参数)得到内容,如果为 \0,则返回字符个数;如果不是 \0,则地址向后移一个字节(因为指针类型为:char*,指针类型决定了指针+1的步长)。继续解引用,直到找到 \0为止。

        在第二个代码块中,分别定义了两个数组,区别是数组一中元素为a b c,数组二的元素为a b c \0(字符串的末尾均有 \0)。根据C语言的标准,arr1是纯粹的字符数组,不能作为字符串使用,因为末尾缺少字符串拥有的 \0。arr2是C语言的合法字符串,末端有 \0。并且arr1和arr2的定义方式的不同,导致arr2涉及只读内存,不可以进行修改,尝试修改会报错:引发未定义行为。

        根据上方的分析,arr1的元素为:a b c  ;arr2的元素为a b c \0。所以调用strlen函数时,arr1因为缺少 \0结尾,所以求出的值为大于3的随机值,并且还会引起越界访问;arr2是C语言中标准的字符串,末尾以 \0结尾,所以得到的结果为字符串 \0之前的字符数: 3。在使用sizeof操作符时,sizeof会求出操作数所占内存的大小,因为上述代码使用sizeof时,操作数均为数组名,则得到的便是整个数组的大小,所以arr1和arr2的sizeof计算结果分别为 3  4。

3.sizeof操作符和strlen函数的对比

sizeof操作符strlen函数
sizeof是操作符strlen是库函数,使用需要包含头文件 string.h 
sizeof 计算操作数所占内存的大小,单位是字节srtlen是求字符串长度的,统计的是 \0 之前字符的个数
不关注内存中存放什么数据关注内存中是否有 \0 
sizeof操作符计入\0srlen函数不计入\0
可以接受变量、表达式或者数据类型只能接受const char *类型的指针

二.数组和指针练习题

1.一维数组

int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a));
printf("%zd\n",sizeof(a+0));
printf("%zd\n",sizeof(*a));
printf("%zd\n",sizeof(a+1));
printf("%zd\n",sizeof(a[1]));
printf("%zd\n",sizeof(&a));
printf("%zd\n",sizeof(*&a));
printf("%zd\n",sizeof(&a+1));
printf("%zd\n",sizeof(&a[0]));
printf("%zd\n",sizeof(&a[0]+1));

        上述代码的具体解释:代码首先创建了一个数组,存放了四个元素1 2 3 4。下面分别是对该一维数组和指针的运算:

        sizeof(a):在数组章节的讲解中,我们说过数组名表示的是数组首元素的地址,但是有两个例外,一个是sizeof(数组名),另一个是&数组名。这两个例外均表示的是整个数组的意思。所以这行代码的意思是:求出整个数组所占内存的大小。------16

        sizeof(a+0):因为数组名没有单独出现在sizeof的括号内,所以此处的数组名表示的是数组首元素的地址(并不是例外情况)。又因为地址的大小是由系统环境决定的(X86 X64),当系统环境为X86环境下时,地址的大小为4个字节;当系统环境为X64环境时,地址的大小为8个字节------4 or 8

        sizeof(*a):因为此情况下数组名表示的是数组首元素的地址,所以对数组首元素地址进行解引用操作,得到的便是数组的首元素。又因为数组首元素的数据类型为int,所以sizeof求出的便是该表达式所占用内存的大小。------4

        sizeof(a+1):因为左侧代码,数组名没有单独出现在sizeof的括号內部,所以数组名就可以理解为数组首元素的地址,因为指针+1跳过的步长是由指针类型决定的,且指针类型为int *,所以a+1表示的是数组首元素的地址跳过4个字节后的地址,也就是数组第二个元素的地址。------4 or 8

        sizeof(a[1]):左侧代码sizeof操作符的操作数为数组的第二个元素,与*(a+1)等价,计算的是数组第二个元素的大小,并且数组元素的类型为int。------4

        sizeof(&a):左侧代码中sizeof操作符的操作数为&a,属于数组名含义理解的例外情况,当&数组名时,数组名表示的不再是数组首元素的地址,而是整个数组的地址。类型为int (*)[4]------4 or 8

        sizeof(*&a):左侧代码因为有&数组名,所以数组名表示的是整个数组的地址。又因为对得到的整个数组的地址进行解引用,便得到了整个数组。所以sizeof计算的就是整个数组所有元素的内存大小。另外一种理解方式:&a的类型为int (*)[4]的数组指针,对此类型数据进行解引用得到的数据是int [4]整个数组。所以最终计算的就是整个数组的大小------16

        sizeof(&a+1):左侧代码因为出现了&数组名的形式,所以数组名表示的是整个数组的地址int (*)[4]。因为该数组指针的类型为int (*)[4],所以该指针+1跳过的便是整个数组。得到的就是指向数组末尾之后的地址。------4 or 8

        sizeof(&a[0]):左侧代码sizeof操作符的操作数为数组首元素的地址(int *类型),所以计算的结果为数组首元素的地址大小------4 or 8

        sizeof(&a[0]+1):左侧代码sizeof操作符的操作数为数组首元素的地址+1,因为数组首元素的地址类型为int *,所以+1之后跳过4个字节,指向的便成了数组第二个元素的地址。所以最终计算的是数组第二个元素的地址大小------4 or 8

2.字符数组

char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr+0));
printf("%zd\n", sizeof(*arr));
printf("%zd\n", sizeof(arr[1]));
printf("%zd\n", sizeof(&arr));
printf("%zd\n", sizeof(&arr+1));
printf("%zd\n", sizeof(&arr[0]+1));

        上述代码的具体解释:首先创建了字符数组,其中字符数组的元素有 a b c d e f ,因为该字符数组不是字符串,所以末尾没有 \0。下面的代码是对该数组和指针的计算。

        sizeof(arr):左侧代码的数组名单独出现在sizeof操作符后的括号内部,所以数组名在此刻表示的是整个数组,并且该字符数组有6个元素,每个元素的数据类型为char。最终计算的结果就是整个数组的大小。------6

        sizeof(arr+0):左侧代码数组名没有单独出现在sizeof后的括号内部,所以此处的数组名表示的是数组首元素的地址。+0依然是数组首元素的地址。最终计算的便是数组首元素地址的大小。------4 or 8

        sizeof(*arr):左侧的数组名表示的数组首元素的地址,进行解引用操作之后得到的便是数组的首元素。且数组元素的数据类型为char*。所以最终计算的是数组首元素的大小------1

        sizeof(arr[1]):左侧代码计算的是数组第二个元素的大小------1

        sizeof(&arr):左侧代码出现了&数组名的格式,所以数组名表示的是整个数组的地址。最终计算的是整个数组地址的大小------4 or 8

        sizeof(&arr+1):左侧代码出现了&数组名的形式,所以数组名在这里表示的是整个数组的地址 类型为:char (*)[6] ,对此数组指针+1,跳过的是整个数组,所以sizeof操作时的指针最终指向的是数组末尾之后的位置。------4 or 8

        sizeof(&arr[0]+1):左侧代码sizeof的操作数为数组首元素的地址+1,因为数组首元素的地址类型为char* ,所以+1跳过了1个字节,最终sizeof操作符的操作数指针指向的是数组第二个元素。------4 or 8

char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n", strlen(arr));
printf("%zd\n", strlen(arr+0));
printf("%zd\n", strlen(*arr));
printf("%zd\n", strlen(arr[1]));
printf("%zd\n", strlen(&arr));
printf("%zd\n", strlen(&arr+1));
printf("%zd\n", strlen(&arr[0]+1));

        上述代码的具体解释:首先创建了一个字符数组,元素有 a b c d e f ,因为不是字符串,所以末尾没有 \0。下面进行了数组和指针的相关计算

        strlen(arr):左侧代码的数组名表示数组首元素的地址,因为该数组没有以 \0结尾,并且未知 \0的位置。------随机值        

        strlen(arr+0):左侧代码和上面一行代码的含义相同,均表示的是数组首元素的地址。并且该数组没有以 \0结尾。------随机值

        strlen(*arr):左侧代码的数组名表示的是数组首元素的地址,对其进行解引用得到的是数组的首元素。因为strlen函数的参数为const char *,所以传给函数的参数就为数组首元素a的ASCII码值:97,接下来就会访问地址为0X61(化为十进制是97)的内容,从而构成非法访问,导致编译器报错------error

        strlen(arr[1]):左侧代码的函数参数为数组的第二个元素:b,因为strlen函数的参数类型为const char *,所以就会把b的ASCII码值当作地址来访问,结果同上构成了非法访问,会导致编译器报错------error

        strlen(&arr):左侧函数参数部分出现了&数组名的形式,所以&arr得到的是整个数组的地址,也就是数组指针,类型为char(*)[6]。又因为strlen的函数参数类型为const char *,所以编译器会报警告(但是依然可以运行)。当强制将数组指针类型作为函数参数传递过去,便会在该指针后找 \0,字符数组没有以 \0结尾。------随机值

        strlen(&arr+1):左侧的函数参数中出现了&arr的形式,得到的是整个数组的地址,是数组指针,类型为 char(*)[6],+1之后跳过整个数组,函数参数的指针指向了数组的末尾之后的位置。strlen函数会从数组末尾开始寻找 \0,至于什么时候、在哪里找到就不得而知了------随机值

        strlen(&arr[0]+1):左侧代码函数参数的含义为数组首元素的地址+1,因为数组首元素的类型为char *,所以+1跳过一个字节,指向的是数组第二个元素的地址。strlen函数会从数组第二个元素开始,向后寻找 \0,直到找到便会返回字符个数。------随机值

char arr[] = "abcdef";
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr+0));
printf("%zd\n", sizeof(*arr));
printf("%zd\n", sizeof(arr[1]));
printf("%zd\n", sizeof(&arr));
printf("%zd\n", sizeof(&arr+1));
printf("%zd\n", sizeof(&arr[0]+1));

        上述代码的具体解释:首先定义了一个字符串,字符串的内容是: a b c d e f \0。因为创建的是字符串,所以末尾以 \0结尾。下面是数组和指针的相关运算。

        sizeof(arr):因为数组名单独出现在sizeof后的括号里,所以这里计算的是整个数组的大小。并且sizeof计算字符串的大小时,会带上末尾的 \0。------7

        sizeof(arr+0):因为数组名没有单独出现在sizeof后面的括号内,所以在这里数组名表示的是数组首元素的地址。------4 or 8

        sizeof(*arr):左侧代码中数组名表示的是数组首元素的地址,对其解引用得到的是数组的首元素。并且数组元素的类型为char*。最终计算的是数组首元素的大小------1

        sizeof(arr[1]):左侧代码计算的是数组第二个元素的大小。------1

        sizeof(&arr):左侧代码出现了&数组名的形式,所以最终计算的是整个数组地址的大小。------4 or 8

        sizeof(&arr+1):左侧代码出现了&数组名的形式,所以&arr取出的是整个数组的地址,所以&arr为数组指针,类型为char(*)[7]。+1将会跳过整个数组,指向数组末尾。因为依然是指针变量,所以大小由操作系统环境决定。------4 or 8

        sizeof(&arr[0]+1):左侧代码的操作数为数组首元素的地址+1,因为数组首元素的地址为char *,所以+1跳过1个字节,所以操作数为指向数组第二个元素的地址。------4 or 8

char arr[] = "abcdef";
printf("%zd\n", strlen(arr));
printf("%zd\n", strlen(arr+0));
printf("%zd\n", strlen(*arr));
printf("%zd\n", strlen(arr[1]));
printf("%zd\n", strlen(&arr));
printf("%zd\n", strlen(&arr+1));
printf("%zd\n", strlen(&arr[0]+1));

        上述代码的具体解释:首先创建了一个字符串,内容是a b c d e f \0。因为是字符串,所以末尾以 \0结尾。下面是数组和指针的计算。

        strlen(arr):左侧代码数组名表示的是数组首元素的地址。strlen函数会从首元素的地址开始,向后寻找 \0,直到找到为止,并返回最后的字符个数------6

        strlen(arr+0):左侧代码和上一个代码的含义是相同的,都是将数组首元素的地址当作函数参数传递给strlen函数。strlen函数会从首元素的地址开始,向后寻找 \0,直到找到为止,并返回最后的字符个数------6

        strlen(*arr):左侧代码中,数组名表示的是数组首元素的地址,进行解引用操作之后的到的就是数组的首元素。因为strlen函数的参数类型为const char *类型,所以函数就会访问地址为数组首元素a的ASCII码值97位置的内容。明显构成了非法访问------error

        strlen(arr[1]):左侧代码中,函数的参数为数组第二个元素。因为strlen函数的参数类型为const char *类型,所以函数就会访问地址为数组首元素b的ASCII码值位置98的内容。明显构成了非法访问。------error

        strlen(&arr):左侧代码中,函数参数出现了&arr,取出的是整个数组的地址。这里就是数组指针,类型为char(*)[7]。因为strlen函数的参数类型为const char *类型,所以这里会将数组指针强制当作const char *传递给strlen函数。因为数组指针的值和数组首元素的值相同,所以之后的strlen函数就会从数组首元素开始向后寻找 \0,直到找到为止,最终计算的就是字符串的字符个数。------6

        strlen(&arr+1):左侧代码中,出现了&数组名的形式,所以取出的是整个数组的地址,为数组指针,类型为char(*)[7],+1跳过了整个数组,函数参数指针指向了整个数组末尾后面的位置。函数会从此开始向后寻找 \0,直到找到为止,返回字符个数------随机值

        strlen(&arr[0]+1):左侧代码的函数参数的含义是取出数组首元素的地址并+1,因为数组首元素地址的类型为char*,所以+1跳过1个字节,所以函数参数指的是数组第二个元素的地址。所以函数就会从第二个元素开始寻找 \0,直到找到为止,返回字符个数。------5

char *p = "abcdef";
printf("%zd\n", sizeof(p));
printf("%zd\n", sizeof(p+1));
printf("%zd\n", sizeof(*p));
printf("%zd\n", sizeof(p[0]));
printf("%zd\n", sizeof(&p));
printf("%zd\n", sizeof(&p+1));
printf("%zd\n", sizeof(&p[0]+1));

        上述代码的具体解释:首先用指针的方式定义了字符串,内容是:a b c d e f \0。因为是字符串,所以末尾是以\0结尾的。并且p的类型为char *指向的是字符串首元素的地址。下面是关于字符串和指针的计算。

        sizeof(p):左侧代码中p是首字符的地址。------4 or 8

        sizeof(p+1):左侧代码中p是首字符的地址,类型为char*。+1跳过1个字节,指向的是字符串第二个字符的地址。------4 or 8

        sizeof(*p):因为p是首字符的地址,所以对其解引用得到的就是首字符:a,最终计算的就是字符a所占空间的大小------1

        sizeof(p[0]):p[0]就等价于*p,所以解释同上------1

        sizeof(&p):左侧代码中p是字符串首字符的地址,是一级指针。对p再次取地址,取的是指针变量p的地址(类型 char**),属于二级指针。------4 or 8

        sizeof(&p+1):左侧代码p是字符串首字符的地址,再次对指针变量取地址,类型变为char * *,+1跳过char*个大小,指向的是指针p变量地址的后面位置。------4 or 8

        sizeof(&p[0]+1):左侧代码操作数为取出首字符的地址后+1,因为首字符的地址类型为char*,+1跳过一个char大小,指向的是字符串的第二个字符的地址。------4 or 8

char *p = "abcdef";
printf("%zd\n", strlen(p));
printf("%zd\n", strlen(p+1));
printf("%zd\n", strlen(*p));
printf("%zd\n", strlen(p[0]));
printf("%zd\n", strlen(&p));
printf("%zd\n", strlen(&p+1));
printf("%zd\n", strlen(&p[0]+1));

        上述代码的具体解释:首先通过指针的形式,定义了一个字符串,p指向的是字符串中首字符的地址,类型为char *。因为是字符串,所以末尾是以 \0结尾的。下面是字符串和指针之间的计算。

        strlen(p):左侧代码strlen函数的参数为p,p为首字符的地址,函数会从此处开始寻找 \0。直到找到返回字符的个数。最终计算的是从开始到 \0的字符串的长度。------6

        strlen(p+1):左侧代码strlen函数的参数为p+1,表示的是字符串第二个字符的地址。函数会从第二个字符开始,寻找\0,直到找到返回字符的个数。最终计算的是从字符串第二个字符开始到 \0的字符串长度。------5

        strlen(*p):左侧代码的p表示的首字符的地址,对其解引用得到的是字符串首字符:a。又因为strlen函数的参数类型为const char *,所以便会将a的ASCII码值97当作地址去访问后面是否由 \0,这样构成了非法访问,会导致编译器的报错。------error

        strlen(p[0]):左侧代码中p[0]与*p是等价的,所以解释同上。------error

        strlen(&p):左侧代码中,p是首字符的地址,再次对其取地址,得到的便是二级指针,类型为char**。将二级指针变量当作函数参数,strlen函数会在指针变量p后寻找 \0,直到找到返回字符的个数。并且指针变量p的情况我们未知------随机值

        strlen(&p+1):左侧代码中表示的是对指针变量p进行取地址,得到二级指针。+1跳过一个char*类型的大小,指向的是二级指针变量后面的位置。函数会从这里开始向后寻找 \0,直到找到返回字符的个数。------随机值

        strlen(&p[0]+1):左侧代码中首先取出首字符的地址,+1跳过了一个char*的大小,指向了字符串的第二个字符。函数会从第二个字符的地址处开始开始向后寻找 \0,直到找到返回字符的个数。最终计算的是第二个字符到末尾 \0的字符串长度。------5

3.二维数组

int a[3][4] = {0};
printf("%zd\n",sizeof(a));
printf("%zd\n",sizeof(a[0][0]));
printf("%zd\n",sizeof(a[0]));
printf("%zd\n",sizeof(a[0]+1));
printf("%zd\n",sizeof(*(a[0]+1)));
printf("%zd\n",sizeof(a+1));
printf("%zd\n",sizeof(*(a+1)));
printf("%zd\n",sizeof(&a[0]+1));
printf("%zd\n",sizeof(*(&a[0]+1)));
printf("%zd\n",sizeof(*a));
printf("%zd\n",sizeof(a[3]));

        上述代码的具体解释:首先创建了3行4列的二维数组,并初始化为0。下面便是数组和指针的计算。

        sizeof(a):左侧代码出现了数组名单独出现在sizeof括号内部的形式,所以此处计算的是整个数组的内存大小。------48

        sizeof(a[0][0]):左侧代码将二维数组的首行首列元素当作sizeof操作符的操作数,在这里计算的是二维数组第一行第一列这个元素的大小。------4

        sizeof(a[0]):左侧代码将二维数组的第一行数组名当作操作数,计算的是第一行整个数组的大小。------16

        sizeof(a[0]+1):左侧代码的a[0]为二维数组的首行数组的数组名,表示的是首行数组首元素的地址,+1跳过该元素,指针指向a[0][1]的位置。------4 or 8

        sizeof(*(a[0]+1)):左侧代码计算的是a[0][1]元素的大小------4

        sizeof(a+1):左侧代码数组名没有单独出现在sizeof后的括号内,表示的是二维数组首元素(第一行数组)的地址,类型为数组指针.+1跳过整个数组,指向的是二维数组第二行数组的地址。类型为int (*)[4]------4 or 8

        sizeof(*(a+1)):左侧代码对数组指针进行解引用,得到的是二维数组第二行数组的整体。最终计算的是二维数组第二行数组的大小------16

        sizeof(&a[0]+1):左侧代码表示的意思是取出二维数组首行数组的地址后+1,因为二维数组的首行数组的地址类型为int (*)[4],是个数组指针。+1就会跳过整个第一行的数组。最终计算的是二维数组第二行数组的地址,类型为int (*)[4]。------4 or 8

        sizeof(*(&a[0]+1)):左侧代码对其(上一条代码)进行解引用,得到的是二维数组第二行数组的整体,最终计算的是二维数组第二行整体的大小------16

        sizeof(*a):左侧代码中,数组名表示的是二维数组首行数组的地址,对其解引用得到的是二维数组的首行数组的整体,最终计算的是二维数组的首行数组的大小------16

        sizeof(a[3]):左侧代码中出现了a[3],大家会觉得是越界访问,但实际是这样吗?不是的!!!在sizeof计算变量、表达式、数据类型的大小时,不会计算内部的表达式,所以a[3]也就不会被访问,sizeof操作符只是通过数据类型确定大小。又因为操作数为二维数组第四行数组的数组名,类型为int [4],且单独出现在sizeof后面的括号内。所以最终计算的是二维数组第四行数组整体的大小。------16

三.指针运算练习题

#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;
}
//程序的结果是什么?  
        上述代码的具体解释:首先定义了一个整型数组,共有5个元素,分别是1 2 3 4 5 。因为a是数组名,定义指针变量时出现了&数组名的形式,所以&a表示的是整个数组的地址,为数组指针,类型为int (*)[5]。+1跳过了整个数组,将此位置(整个数组末尾)的指针变量强制转化为int *类型,赋值给ptr指针变量。                                                                                              在打印数据时,*(a + 1)中的a表示的是数组首元素的地址,+1跳过了一个元素,指向的是数组第二个元素的地址,进行解引用便会得到数组的第二个元素------2。*(ptr - 1)中ptr表示的是整个数组末尾,指针类型为int *,-1指针位置的返回4个字节,指向数组最后一个元素。进行解引用便会得到------5
//在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);printf("%p\n", (unsigned long)p + 0x1);printf("%p\n", (unsigned int*)p + 0x1);return 0;
}

        上述代码的具体解释:首先定义了结构体变量,然后又定义了一个结构体指针变量,且先将地址0x100000强制类型转化为struct Test*,后赋值给结构体指针变量p。

        p + 0x1表示的是结构体指针变量+0x1,已知指针变量+1,跳过的是该指针类型的大小sizeof(struct Test*)(题目已知为20字节)。因为p的数值为16进制的地址,而20为十进制数字,所以需要将十进制的数字化为16进制的数字后再进行相加。------0X100014

        (unsigned long)p + 0x1:将结构体指针变量强制类型转化为无符号长整形后+1,这种运算为普通的整型之间的运算,不再是指针运算。0X100000+0X1------0X100001

        (unsigned int*)p + 0x1):p原本为struct Test*类型,现在被强制类型转化为unsigned int*类型,所以+1跳过的大小为sizeof(unsigned int*)=4个字节------0X100004

#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;
}

        上述代码的具体解释:首先创建了一个3行2列的二维整型数组,但是这里有个陷阱:内部有小括号。我们知道有小括号时,括号最后的结果为最后一个表达式的计算结果。所以实际上,二维数组的元素为1 3 5 0 0 0 。二维数组的首行数组名就是二维数组首行数组首元素的地址,将其赋值给指针变量p。p[0]就等价于*p,对二维数组首行数组首元素进行解引用,得到的就是二维数组的首元素------1

//假设环境是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]);return 0;
}

        上述代码的具体解释:首先创建了5行5列的二维数组,这里的a表示的是二维数组的首行元素的地址,类型为int (*)[4],将其赋值给指针变量p。p是数组指针,类型为int(*)[4],所以每+1跳过4个元素。又因为p[4][2]=*(*(p+4)+2),所以上图中蓝色为该元素的位置。又因为指针计算的性质:两指针相减得到的是两指针之间元素的个数。又因为printf函数的占位符不同,所以打印的结果就不同。%p 是打印地址时使用的占位符,所以打印结果便会认为是无符号16进制的数字;%d打印的是有符号的整型。因为两者相隔4个元素,并且小地址在前,所以是-4。-4的原码为1000 0000 0000 0000 0000 0000 0100;-4的反码是1111 1111 1111 1111 1111 1111 1011;-4的补码:1111 1111 1111 1111 1111 1111 1100化成16进制为:FFFFFC。

        所以最终的结果为:FFFFFC -4。

#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;
}

        上述代码的具体解释:首先创建了一个2行5列的二维数组,&aa取出的是整个二维数组的地址,+1指针变量指向了整个二维数组末尾的位置。aa为二维数组的数组名,表示的是二维数组首行数组的地址,为数组指针,类型为int (*)[5],+1跳过整行数组,指针变量指向二维数组的第二行数组。所以最终的结果为5 10

#include <stdio.h>
int main()
{char *a[] = {"work","at","alibaba"};char**pa = a;pa++;printf("%s\n", *pa);return 0;
}

        上述代码的具体解释:创建了一个字符指针数组,共有三个元素分别是work at alibaba三个字符串的首字符的地址。pa为二级指针,用于存放指针变量a首地址。pa++之后,因为pa的类型为char **,所以跳过1个字节,指向了a[1]的地址。最终的结果为pa变量的解引用结果,并且占位符为%s,所以最终的结果为at。

#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;
}

        上述代码的具体解释:首先创建了一个字符指针数组,用于存放"ENTER","NEW","POINT","FIRST"四个字符串的首字符的地址。c为数组名,表示的是数组首元素(ENTER字符串的首字符地址)的地址。+1表示跳过char*[ ]个大小。所以二级字符指针数组cp的元素为c中首字符地址变量的指针。最后创建了一个三级指针,存放cp二级指针变量的地址。

        在调用printf函数时,变量的自增或自减运算会影响最初始的值,所以上述的图片为最初始情况的解析。

        第一次调用printf函数,对cpp变量进行自增后解引用,所以cpp现在指向了cp的第二个元素:c+2。又因为占位符是%s,解引用后内容为c数组的第三个元素。所以打印的结果为:POINT

        第二次调用printf函数,操作符的优先级见上述第二张图片。cpp先自增,因为在上一下printf函数调用时,cpp自增了一次,指向了cp的第二个元素,所以此次自增将指向cp的第三个元素。解引用之后,将得到的c+1自减1,所以cp数组第三个元素被改为了c,对c进行解引用,得到了c数组的第一个元素(ENTER字符串的首地址),后需要+3,对字符指针变量+3,会跳过3个字节,从开始的指向ENTER开始的E变为指向ENTER中间的E。然后根据printf函数的%s占位符得知,最终打印的结果为:ER。

        第三次调用printf函数,cpp[-2]等价于*(cpp-2),因为cpp经过上述两次printf函数的调用,使得cpp指向的位置是cp数组的第3个元素,cpp-2计算得到了指向cp数组的首元素的指针(这里并没有改变cpp的值,此时的cpp依然指向cp数组的第三个元素)。然后进行解引用得到的是c数组最后一个元素(FIRST字符串首字符的地址),最后进行+3操作,因为此时的指针数组元素变量的类型为char *,所以跳过3个字符,指向字符S。最终打印的结果为:ST。

        第四次调用printf函数,cpp[-1][-1]等价于*(*(cpp-1)-1),因为cpp经过上述的三次printf函数调用,指向为cp数组的第三个元素,所以*(cpp-1)表示的是cp数组第二个元素c+2,再次进行-1后,使得cp数组的第二个元素变为c+1,解引用后得到的是数组c的第二个元素(NEW首字符的地址),最后进行+1,使得指针指向了NEW第二个字符的位置,最终的打印结果为EW。

http://www.lryc.cn/news/622065.html

相关文章:

  • linux服务器查看某个服务启动,运行的时间
  • Chrome插件开发
  • 最长递增子序列-dp问题+二分优化
  • 智能巡检技术浅析
  • 最新chrome浏览器elasticsearch-head无法安装使用问题
  • 牛市暴跌后什么时候进入好
  • C++ 调试报错 常量中有换行符
  • NAS播放器的新星,一站式全平台媒体库管理工具『Cinemore』体验
  • 高通vendor app访问文件
  • 前端css学习笔记6:盒子模型
  • IP生意的天花板更高了吗?
  • 多路混音声音播放芯片型号推荐
  • Microsoft Visual Studio常用快捷键和Windows系统常用快捷键的整理
  • 深入解析五大通信协议:TCP、UDP、HTTP_HTTPS、WebSocket与GRPC
  • 【Leetcode hot 100】53.最大子数组和
  • 异步任务执行顺序
  • DataGear:一个免费开源的国产数据可视化分析平台
  • 经典BT的连接过程
  • 归并排序和统计排序
  • 智能工厂生产监控大屏-vue纯前端静态页面练习
  • AI智能体在软件测试中的应用与未来趋势
  • 开疆智能Ethernet转ModbusTCP网关连接测联无纸记录仪配置案例
  • python-pycharm切换python各种版本的环境与安装python各种版本的环境(pypi轮子下载)
  • C++第二十课:快递运费计算器 / 黑白配+石头剪刀布小游戏
  • SAP ALV导出excel 报 XML 错误的 /xl/sharedStrings.xml
  • 2025.08.09 江门两日游记
  • 4.2 寻址方式 (答案见原书 P341)
  • LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。
  • 硬件实现webrtc的编解码
  • 从前端框架到GIS开发系列课程(26)在mapbox中实现地球自转效果,并添加点击事件增强地图交互性