指针详解4

运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主

1.sizeof和strlen的对比

  • 1.1 sizeof

    • sizeof用于计算变量占据内存空间的大小,单位是字节。如果操作数是类型的话,计算的是使用该类型创建的变量所占内存空间的大小

    • sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据

    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int a = 0;
        printf("%zd\n", sizeof(a));
        printf("%zd\n", sizeof a);
        printf("%zd\n", sizeof(int));
        
        return 0;
    }
    

    image

  • 1.2 strlen

    • strlen是C语言库函数,功能是求字符串长度,函数原型为size_t strlen(const char *str);

    • 计算从函数的参数str指向的地址开始往后,直到\0之前的字符个数

    • strlen函数会一直向后找\0字符,直到找到为止,因此可能存在越界查找

    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        char arr1[3] = { 'a', 'b', 'c' };
        char arr2[] = "abc";
    
        printf("%zd\n", strlen(arr1));
        printf("%zd\n", strlen(arr2));
    
        printf("%zd\n", sizeof(arr1));
        printf("%zd\n", sizeof(arr2));  
    
        int arr[] = { 1, 2, 3, 4, 5, 0};
        printf("%zd\n", strlen(arr));    // 1
        // 参考数组在内存中的分布,01即00000001,是1个字节,后面的0会被看成'\0'
        return 0;
    }
    

    image

    image

    sizeof strlen
    1.sizeof是操作符 1.strlen是库函数,使用前需要包含头文件string.h
    2.sizeof计算操作数所占内存的大小,单位是字节 2.strlen用于求字符串长度,统计\0之前的字符个数
    3.不关注内存中存放什么数据 3.关注内存中是否有\0,如果没有\0,就会持续向后找,可能会越界

2.数组和指针题目解析

  • 2.1 数组名和意义

    • sizeof(数组名)中的数组名表示整个数组,计算的是整个数组的大小

    • &数组名中的数组名表示整个数组,取出的是整个数组的地址

    • 除以上两种情况外,所有的数组名都表示首元素的地址

  • 2.2 一维数组

#include <stdio.h>

int main(int argc, const char * argv[]) {
    int a[] = {1, 2, 3, 4};
    printf("%zd\n", sizeof(a)); // 16
    // 数组名单独放在sizeof内部,a表示整个数组,计算整个数组占用内存的大小,即4B*4=16B

    printf("%zd\n", sizeof(a + 0)); // 4或8
    // a是数组名,未单独放在sizeof内部,表示数组首元素的地址,加上0依然是首元素的地址,计算该地址占用内存的大小

    printf("%zd\n", sizeof(*a));    // 4   *(a + 0) = a[0]
    // a表示数组首元素的地址,解引用后取到首元素,int型长度为4B

    printf("%zd\n", sizeof(a + 1)); // 4或8
    // a表示数组首元素的地址,a+1表示第2个元素的地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(a[1]));  // 4 计算第2个元素的长度

    printf("%zd\n", sizeof(&a));    // 4或8
    // a与&放一块,这里的a表示整个数组,&a就是整个数组的地址,地址占据内存长度为4B或8B    

    printf("%zd\n", sizeof(*&a));   // 16
    // 思路1:*&a的*和&抵消,就是sizeof(a)
    // 思路2:&a是数组的地址,类型为int (*)[4],*&a就是访问这个数组

    printf("%zd\n", sizeof(&a + 1));    // 4或8
    // &a是数组的地址,&a+1是跳过整个数组后新位置的地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(&a[0]));     // 4或8
    // 第1个元素的地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(&a[0] + 1)); // 4或8
    // 第2个元素的地址,地址占据内存长度为4B或8B

    return 0;
}

image

  • 2.3 字符数组
// 代码1
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char arr[] = { 'a','b','c','d','e','f' };
    
    printf("%zd\n", sizeof(arr));    // 6
    // arr单独放在sizeof内部,表示整个数组,占据内存6B

    printf("%zd\n", sizeof(arr + 0));    // 4或8
    // arr表示数组首元素的地址,加0依然是首元素地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(*arr));    // 1
    // arr未单独放在sizeof内部,它表示数组首元素的地址,解引用取到了首元素,char型占据内存1B

    printf("%zd\n", sizeof(arr[1]));    // 1
    // 计算字符'b'占据内存的长度

    printf("%zd\n", sizeof(&arr));    // 4或8
    // &arr表示整个数组的地址,地址占据内存长度为4B或8B
  
    printf("%zd\n", sizeof(&arr + 1));    // 4或8
    // &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(&arr[0] + 1));    // 4或8
    // &arr[0] + 1即&arr[1],地址占据内存长度为4B或8B

    return 0;
}

image

// 代码2
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char arr[] = { 'a','b','c','d','e','f' };
    printf("%zd\n", strlen(arr));    // 随机值,字符数组中没有 '\0'
    printf("%zd\n", strlen(arr + 0));    //随机值,同上

    // printf("%zd\n", strlen(*arr));    // 程序崩溃,无法运行
    // arr是数组名表示首元素的地址,*arr即首元素'a',其值为97
    // 强行将97传递给strlen,函数会将97认做地址,然后去访问内存,但这是非法访问,程序奔溃

    printf("%zd\n", strlen(arr[1]));    // 同上,程序崩溃,无法运行
    // 强行将字符'b'的值98传递给strlen,非法访问

    printf("%zd\n", strlen(&arr));    // 随机值
    // &arr 表示整个数组的地址,在数值上和数组首元素地址相等,由此向后找'\0',但字符数组中没有 '\0',因此是随机值

    printf("%zd\n", strlen(&arr + 1));    //随机值
    // &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,从它开始向后找'\0'

    printf("%zd\n", strlen(&arr[0] + 1));    //随机值
    // &arr[0] + 1即&arr[1],从第2个数组元素开始往后找'\0',但字符数组中没有 '\0',因此是随机值

    return 0;
}

image

// 代码3
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char arr[] = "abcdef";    // 字符串末尾存在'\0'

    printf("%zd\n", sizeof(arr));    // 7
    // arr单独放在sizeof内部表示整个数组,占据内存7B

    printf("%zd\n", sizeof(arr + 0));    // 4或8
    // arr未单独放在sizeof内部,它表示数组首元素的地址,加0依然是首元素地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(*arr));    // 1
    // 思路1:arr是首元素的地址,解引用后取到首元素,占据内存1B
    // 思路2:*arr --> *(arr + 0) --> arr[0]

    printf("%zd\n", sizeof(arr[1]));    // 1 下标为1的字符,占据内存1B
    
    printf("%zd\n", sizeof(&arr));    // 4或8
    // &arr表示整个数组的地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(&arr + 1));    // 4或8
    // &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(&arr[0] + 1));    // 4或8
    // 即&arr[1],是一个地址,占据内存长度为4B或8B
    return 0;
}

image

// 代码4
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char arr[] = "abcdef";    // 字符串末尾存在'\0'

    printf("%zd\n", strlen(arr));    // 6
    // arr表示首元素的地址,从字符'a'开始往后统计'\0'之前的字符个数,共6个

    printf("%zd\n", strlen(arr + 0));    // 6
    // arr与arr+0均表示首元素的地址,从字符'a'开始往后统计'\0'之前的字符个数,共6个

    printf("%zd\n", strlen(*arr));    // 程序崩溃
    // *arr --> *(arr+0) --> 'a' --> 97, 97作为地址传给给了strlen,但该地址不能访问
    
    printf("%zd\n", strlen(arr[1]));    // 程序崩溃,理由同上

    printf("%zd\n", strlen(&arr));    // 6
    // &arr表示整个数组的地址,与数组首元素的地址指向同一个位置,从字符'a'开始往后统计'\0'之前的字符个数,共6个

    printf("%zd\n", strlen(&arr + 1));    // 随机值
    // &arr表示整个数组的地址,&arr+1表示跳过整个数组后新位置的地址,此处存储内容未知,不知道'\0'在何处,因此结果不确定

    printf("%zd\n", strlen(&arr[0] + 1));    // 5
    // &arr[0] + 1 即 &arr[1],从字符'b'开始往后统计'\0'之前的字符个数,共5个
    return 0;
}

image

// 代码5
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char *p = "abcdef";    // p指向字符串字面量的第1个字符

    printf("%zd\n", sizeof(p));    // 4或8
    // p是指针变量,即地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(p + 1));    // 4或8
    // p是指针变量,p+1指向'b',地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(*p));    // 1
    // *p --> *(p+0) --> p[0] --> 'a',字符型变量占据内存1B

    printf("%zd\n", sizeof(p[0]));    // 1 同上

    printf("%zd\n", sizeof(&p));    // 4或8
    // p是指针变量,&p是指针变量的地址,即地址的地址,也是二级指针char **,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(&p + 1));    // 4或8
    // &p+1也是地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(&p[0] + 1));    // 4或8
    // &p[0] + 1即p+1,地址占据内存长度为4B或8B

    return 0;
}

image

// 代码6
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char* p = "abcdef";    // p指向字符串字面量的第1个字符

    printf("%zd\n", strlen(p));    // 6
    // 从'a'开始计算'\0'之前的字符个数

    printf("%zd\n", strlen(p + 1));    // 5
    // 从'b'开始计算'\0'之前的字符个数

    printf("%zd\n", strlen(*p));    // 程序崩溃
    // *p --> *(p+0) --> p[0] --> 'a' --> 97 将97作为地址传给strlen,非法访问

    //printf("%zd\n", strlen(p[0]));    // 程序崩溃,同上

    printf("%zd\n", strlen(&p));    // 随机值
    // &p是指针变量的地址,即地址的地址,此处存储内容和'\0'的位置均未知

    printf("%zd\n", strlen(&p + 1));    // 随机值,同上
    
    printf("%zd\n", strlen(&p[0] + 1));    // 5
    // &p[0] + 1即p + 1,从'b'开始计算'\0'之前的字符个数

    return 0;
}

image

  • 2.4 二维数组
#include <stdio.h>

int main(int argc, const char * argv[]) {
    int a[3][4] = { 0 };

    printf("%zd\n", sizeof(a));    // 48
    // a单独放在sizeof内部表示整个数组,求整个数组占据内存的大小,4B*12=48B

    printf("%zd\n", sizeof(a[0][0]));    // 4
    // a[0][0]即第1行第1列元素0,整型占据内存4B

    printf("%zd\n", sizeof(a[0]));    // 16
    // a[0]是第1行的数组名,单独放在sizeof内部表示整个第1行,共4B*4=16B

    printf("%zd\n", sizeof(a[0] + 1));    // 4或8
    // a[0]是第1行的数组名,未单独放在sizeof内部,表示&a[0][0],加1后即&a[0][1],地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(*(a[0] + 1)));    // 4
    // a[0] + 1即&a[0][1],解引用后为a[0][1],整型占据内存4B

    printf("%zd\n", sizeof(a + 1));    // 4或8
    // a是数组名,未单独放在sizeof内部,表示首元素的地址,即第1行的地址。加1后表示第2行的地址,地址占据内存长度为4B或8B
    printf("%zd\n", sizeof(*(a + 1)));    // 16
    // a+1即第2行的地址,解引用后取到了第2行,4B*4=16B

    printf("%zd\n", sizeof(&a[0] + 1));    // 4或8 第2行的地址
    // a[0]是第1行的数组名,&a[0]表示第1行数组的地址,加1后是第2行的地址,地址占据内存长度为4B或8B

    printf("%zd\n", sizeof(*(&a[0] + 1)));    // 16
    // &a[0] + 1即第2行的地址,解引用后取到了第2行,4B*4=16B

    printf("%zd\n", sizeof(*a));    // 16
    // a是数组名,未单独放在sizeof内部,表示数组首元素的地址,即第1行的地址。解引用后取到了第1行,4B*4=16B

    printf("%zd\n", sizeof(a[3]));    // 16
    // a[3]即行标为3的整行的地址,越界,但sizeof内部的表达式不会真实计算,4B*4=16B

    return 0;
}

image

3.指针运算题目解析

  • 3.1 分析以下程序的运行结果
// &a + 1的类型是数组指针int (*)[5],代码将其强制转换为整形指针int *
#include <stdio.h>

int main(int argc, const char * argv[]) {
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));    // 打印结果:2,5
    return 0;
}

image

  • 3.2 在x86环境下,假设结构体的大小是20B,分析以下程序的输出结果
#include <stdio.h>

struct Test {
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
} * p = (struct Test*)0x100000;
// 将0x100000地址强制转为结构体指针变量p


int main(int argc, const char * argv[]) {
    // p指向了结构体,跳过1个单位即20B的结构体空间,以十六进制表示20为14
    // x86环境意为32bit的地址总线,0x00100000 + 0x00000014 = 0x00100014
    printf("%p\n", p + 0x1);
    
    // 将p强制转为无符号长整型,这样0x100000不再表示地址,而是普通整数
    // 整数加1即数值加1,0x0010000 + 0x00000001 = 0x00100001
    printf("%p\n", (unsigned long)p + 0x1);

    // 将p从结构体指针强制转换为无符号整型指针,0x100000依然是地址
    // 整型指针加1即跳过4B,0x00100000 + 0x00000004 = 0x00100004
    printf("%p\n", (unsigned int*)p + 0x1);

    return 0;
}

image

  • 3.3 分析以下程序的运行结果
#include <stdio.h>

int main(int argc, const char * argv[]) {
    // 数组元素是逗号表达式,先简化为 {1, 3, 5, 0, 0, 0}
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p = NULL;

    // a[0]是第1行数组的数组名,指向一维数组的第1个元素
    p = a[0];
    printf("%d", p[0]);    // 打印结果:1

    return 0;
}

image

  • 3.4 在x86环境下,分析以下程序的输出结果
#include <stdio.h>

int main(int argc, const char * argv[]) {
    int a[5][5] = {0};
    int (*p)[4] = {NULL};

    // a是二维数组的数组名,表示数组首元素的地址,即第1行的地址,是数组指针类型 int (*)[5]
    p = a;    // 两者类型不一致,会有警告

    // 由指向图可知两数组元素存储位置间隔4个,且后方地址更大,以十进制数打印时结果为-4
    // 以地址打印时,会打印-4的补码,地址没有负数,按照源码、反码和补码的运算规则得到0xfffffffc
    //  -4  10000000 00000000 00000000 00000100
    //      11111111 11111111 11111111 11111100
    //      ff       ff       ff       fc
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    
    return 0;
}

image

image

  • 3.5 分析以下程序的运行结果
#include <stdio.h>

int main(int argc, const char * argv[]) {
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    // &aa表示整个数组的地址,加1后指向了整个数组末尾,并强制转为整型指针
    int* ptr1 = (int*)(&aa + 1);

    // aa表示数组首元素(第1行)的地址,加1后为第2行的地址,解引用后取到了第2行,隐式转换成第2行首元素的地址
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));    // 打印结果:10,5
    return 0;
}

image

  • 3.6 分析以下程序的执行结果
#include <stdio.h>

int main(int argc, const char * argv[]) {
    // a是字符指针数组,其每个元素是字符指针(地址),它各指向了3个字符串
    char* a[] = { "work", "at", "alibaba" };

    // pa是二级指针,它指向了字符数组a中第一个元素,即work中'w'的首地址
    char** pa = a;

    // pa原来的指向失效,重新指向了at中'a'的首地址
    pa++;
    printf("%s\n", *pa);    // 打印结果:at
    return 0;
}

// 图中的地址均为假设

image

image

  • 3.7 分析以下程序的执行结果
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char* c[] = { "ENTER","NEW","POINT","FIRST" };
    char** cp[] = { c + 3,c + 2,c + 1,c };
    char*** cpp = cp;
    
    // cpp存储的是cp的地址,它指向了cp,也指向了cp[0]。++cpp后新的cpp指向了cp[1]
    // 第1次解引用取到了cp[1]的值,即0xcfe2,这是一个地址,指向了c[2]
    // 第2次解引用取到了c[2]的值,即0x100B,这也是一个地址,指向了POINT,打印一直到'\0'之前的字符
    printf("%s\n", **++cpp);    //打印结果:POINT

    // 上文分析cpp指向了cp[1],继续++cpp后新的cpp指向了cp[2]
    // 第1次解引用取到了cp[2]的值,即0xcfde,这是一个地址,指向了c[1]
    // c[1]的地址自减之后指向c[0],即0xcfda,也是一个地址,指向了c[0]
    // 第2次解应用取到了c[0]的值,即0x1001,这个地址指向了ENTER
    // 最后加3,地址变为0x1004,指向了字符串"ENTER"的字符'E',打印一直到'\0'之前的字符
    printf("%s\n", *-- * ++cpp + 3);  // ER

    // *cpp[-2] + 3即*(*(cpp - 2)) + 3,上文分析cpp指向了cp[2],cpp-2指向了cp[0],即0x31f2
    // 第1次解引用取到了0xcfe6,这是一个地址,指向了c[3]
    // 第2次解引用后取到了0x1011,也是一个地址,指向了FIRST
    // 最后加3,地址变为0x1014,指向了字符串"FIRST"的字符'S',打印一直到'\0'之前的字符
    printf("%s\n", *cpp[-2] + 3);  // ST

    // cpp[-1][-1] + 1即*(*(cpp - 1) - 1) + 1,上文分析cpp指向了cp[2],cpp-1指向了cp[1],即0x31f6
    // 第1次解引用取到了0xcfe2,这是一个地址,指向了c[2],c[2]的地址再减1是c[1]的地址0xcfde
    // 第1次解引用取到了c[1]的值0x1007,也是一个地址,指向了NEW
    // 最后加1,地址变为0x1008,指向了字符串"NEW"的字符'E',打印一直到'\0'之前的字符
    printf("%s\n", cpp[-1][-1] + 1);  // EW

    return 0;
}

// 图中的地址均为假设

image

image

posted @ 2025-08-21 17:32  pycoder_666  阅读(27)  评论(0)    收藏  举报