指針詳解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.h2. 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;
}

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

// 代碼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;
}

// 代碼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;
}

// 代碼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;
}

// 代碼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;
}

// 代碼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;
}

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

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

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

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

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


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

- 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;
}
// 圖中的地址均為假設


- 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;
}
// 圖中的地址均為假設






浙公網安備 33010602011771號