sizeof和strlen
sizeof
sizeof是用來計算變量所占內存空間大小的,單位是字節,如果操作數是類型,計算的是使用類型創建的變量所占內存空間的大小。
sizeof只關注占用內存空間的大小,不在乎內存中存放什么數據。
我們來看一下這個代碼,它的運行結果是多少?
#include<stdio.h>
int main()
{int a = 10;printf("%zu\n", sizeof(a + 3.14));return 0;
}
a是int類型的數據,3.14是double類型的數據,兩者進行運算時,會將int類型的數據提升為double類型,所以最后結果的類型是double類型,所以最后結果應該是8.
strlen()
strlen 是 C 語言庫函數,功能是求字符串長度。函數原型如下:
?size_t strlen (const char * str);
統計的是從 strlen 函數的參數 str 中這個地址所指向的元素開始向后找,直到\0 之前字符串中字符的個數。
strlen 函數會一直向后找 \0 字符,直到找到為止,所以可能存在越界查找。
看以下代碼,運行結果啥?
#include <stdio.h>
#include <string.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;
}
arr1是一個字符數組,末尾沒有'\0',而strlen會一直向后找'\0',直到找到,所以使用strlen(arr1)的結果應該是一個隨機值。
arr2是一個字符串,字符串的末尾含有'\0',所以strlen(arr2)的結果為3。
我們之前說過,sizeof(數組名)中,數組名表示整個數組,計算結果使整個數組的大小。
所以,sizeof(arr1)=3,sizeof(arr2)=4.
sizeof和strlen對比
sizeof | strlen |
1. sizeof 是操作符 2. sizeof 計算操作數所占內存的大小,單位是字節 3. 不關注內存中存放什么數據 | 1. strlen 是庫函數,使用需要包含頭文件 string.h 2. strlen 是求字符串長度的,統計的是 \0 之前字符的個數 3. 關注內存中是否有 \0,如果沒有 \0,就會持續往后找,可能會越界 |
數組和指針試題
在此之前,還是提醒一下大家:
數組名的意義
- sizeof (數組名),數組名單獨放在sizeof()中,這里的數組名表示整個數組,計算的是整個數組的大小。
- & 數組名,這里的數組名表示整個數組,取出的是整個數組的地址。
- 除此之外所有的數組名都表示首元素的地址。
- 題目1:下面代碼的運行結果
int main()
{int a[] = { 1,2,3,4 };printf("%zu\n", sizeof(a));printf("%zu\n", sizeof(a + 0));printf("%zu\n", sizeof(*a));printf("%zu\n", sizeof(a + 1));printf("%zu\n", sizeof(a[1]));printf("%zu\n", sizeof(&a));printf("%zu\n", sizeof(*&a)); printf("%zu\n", sizeof(&a + 1));printf("%zu\n", sizeof(&a[0]));printf("%zu\n", sizeof(&a[0] + 1)); return 0;
}
解釋:
16? ? ?//數組名單獨放在sizeof()中,所以計算的是整個數組的大小,結果是4*4=16
8/4? ? //這里的數組名并沒有單獨放在sizeof()中,表示的是首元素的地址,a+0表示地址偏移量為0,所以表示的是首元素的地址,結果是指針的大小,即4/8
4? ? ? ?//這里的數組名并沒有單獨放在sizeof()中,表示首元素的地址,*a得到首元素a[0],這是一個整型變量,大小為4個字節
8/4? ? //a沒有單獨放在sizeof()中,表示首元素的地址,a+1表示地址偏移量為1,所以表示的是第二個元素的地址,結果是指針的大小,即4/8
4? ? ?//a[1]表示的是數組里面的整型元素,故結果為4
8/4? //&a取出的是整個數組的地址,既然是地址,那就是指針類型的數據,指針大小都是4/8
16? ?//&a取出的是整個數組的地址,那么它的類型就是數組指針:int(*)[4],那么對&a進行解引用,得到的就是整個數組,整個數組的大小自然就是16
8/4? //&a取出的是整個數組的地址,那么它的類型就是數組指針,&a+1表示要跳過整個數組的大小,但它的類型還是指針,所以結果為4/8
8/4? //a[0]是整型變量,它的地址類型就是整型指針,指針的大小就是4/8
8/4 //&a[0]是第一個元素的地址,&arr[0]+1表示的是第二個元素的地址,指針的大小就是4/8
- 題目2:下面代碼的運行結果
#include <stdio.h>
int main()
{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));return 0;
}
這道題跟上面那道題做法幾乎相同,我們再來看一下:
6? ? ? //數組名單獨放在sizeof()中,所以計算的是整個數組的大小,結果是1*6=6
8/4? ?//這里的數組名并沒有單獨放在sizeof()中,表示的是首元素的地址,arr+0表示地址偏移量為0,所以表示的是首元素的地址,結果是指針的大小,即4/8
1? ? ?//這里的數組名并沒有單獨放在sizeof()中,表示首元素的地址,*a得到首元素a[0],這是一個整型變量,大小為1個字節
1? ? //arr[1]表示的是數組里面的整型元素,故結果為1
8/4? //&arr取出的是整個數組的地址,既然是地址,那就是指針類型的數據,指針大小都是? ? ? ? ? ? ? 4/8
8/4? //&arr取出的是整個數組的地址,那么它的類型就是數組指針,&a+1表示要跳過整個數組的大小,但它的類型還是指針,所以結果為4/8
8/4? ?//&arr[0]是第一個元素的地址,&arr[0]+1表示的是第二個元素的地址,指針的大小就是? ? ? ? ? ? ?4/8
- 題目3:下面代碼的運行結果
#include <stdio.h>
#include <string.h>int main()
{char arr[] = { 'a','b','c','d','e','f' };
//1.printf("%zu\n", strlen(arr));
//2.printf("%zu\n", strlen(arr + 0));
//3.printf("%zu\n", strlen(*arr));
//4.printf("%zu\n", strlen(arr[1]));
5.printf("%zu\n", strlen(&arr));
6.printf("%zu\n", strlen(&arr + 1));
7.printf("%zu\n", strlen(&arr[0] + 1));return 0;
}
解釋:
//這個字符串中沒有\0,strlen就會持續往后找\0,就會越界,所以結果是隨機值
//arr和arr+0表示的都是首元素的地址,所以在同一測試環境行下,這和第一個結果一樣,都是隨機值
//*arr表示的是首元素a,a的ASCII碼值是97,是一個整型,但是strlen需要接收的是一個字符指針類型的地址,所以會把97當成一個地址,但這塊地址可能不屬于當前內存,這就造成了非法訪問,導致程序崩潰,如果要繼續測試下面的代碼,就要注釋掉這句代碼
//和第三個結果一樣,會導致程序崩潰,如果要繼續測試下面的代碼,就要注釋掉這句代碼
//&arr的類型是數組指針類型:char(*)[],但是它里面存放的值和arr是一樣的,而strlen需要接受的是char*類型的指針,所以會把&arr的結果當做char*類型來處理,所以在同一測試環境行下,這和第一個結果一樣,都是隨機值
//&arr+1表示跳過了整個字符數組,也就是跳過了6個字符元素,但它的類型仍然和&arr類型相同,而strlen需要接受的是char*類型的指針,所以會把&arr+1的結果當做char*類型來處理,然后往后找\0,所以結果是隨機值,而且在同一測試環境行下,這和第一個結果相差6(整個字符數組的大小)
//&arr[0]+1表示的是第二個元素的地址,這個字符串中沒有\0,strlen就會持續往后找\0,就會越界,所以結果是隨機值,而且在同一測試環境行下,這和第一個結果相差1
- 題目4:下面代碼的運行結果
#include <stdio.h>
int main()
{char arr[] = "abcdef";printf("%zu\n", sizeof(arr));//printf("%zu\n", sizeof(arr + 0));printf("%zu\n", sizeof(*arr));printf("%zu\n", sizeof(arr[1]));printf("%zu\n", sizeof(&arr));printf("%zu\n", sizeof(&arr + 1));printf("%zu\n", sizeof(&arr[0] + 1));return 0;
}
這個代碼的結果分析與前面代碼的分析一樣,我們就不再贅述,如果有不懂的,歡迎在評論區留言哦。這里我們直接給出答案:
7
8/4
1
1
8/4
8/4
8/4
- 題目5:下面代碼的運行結果
#include <stdio.h>
#include <string.h>
int main()
{char arr[] = "abcdef";printf("%zu\n", strlen(arr));printf("%zu\n", strlen(arr + 0));printf("%zu\n", strlen(*arr));printf("%zu\n", strlen(arr[1]));printf("%zu\n", strlen(&arr));printf("%zu\n", strlen(&arr + 1));printf("%zu\n", strlen(&arr[0] + 1));return 0;
}
//arr表示首元素的地址,由于這是字符串,末尾隱藏著一個\0,所以計算的是\0之前的字符個數,結果是6
//arr+0表示首元素的地址,由于這是字符串,末尾隱藏著一個\0,所以計算的是\0之前的字符個數,結果是6
//和題目三中結果一樣,會導致程序崩潰
//會導致程序崩潰
//&arr的類型是數組指針類型,但是它里面存放的值和arr是一樣的,而strlen需要接受的是char*類型的指針,所以會把&arr的結果當做char*類型來處理,所以在同一測試環境行下,這和第一個結果一樣
//&arr+1表示跳過了整個字符數組,也就是跳過了7個字符元素,但它的類型仍然和&arr類型相同,而strlen需要接受的是char*類型的指針,所以會把&arr+1的結果當做char*類型來處理,然后往后找\0,所以結果是隨機值
//&arr[0]+1表示第二個元素的地址,所以結果比第一個的少1,即結果是5
- 題目6:下面代碼的運行結果
#include <stdio.h>
int main()
{char* p = "abcdef";printf("%zu\n", sizeof(p));printf("%zu\n", sizeof(p + 1));printf("%zu\n", sizeof(*p));printf("%zu\n", sizeof(p[0]));printf("%zu\n", sizeof(&p));printf("%zu\n", sizeof(&p + 1));printf("%zu\n", sizeof(&p[0] + 1));return 0;
}
解釋:
8/4? ? ?//p是指針變量,里面存放的是a的地址,所以結果是8/4
8/4? ? ?//p+1仍然是指針,里面存放的是b的地址,所以結果是8/4
1? ? ? ?//*p的結果是a,a是字符型變量,所以結果是1
1? ? ? //p[0]等價于*(p+0),得到的是a,所以結果是1
8/4? ? ?//&p是二級指針,也是地址,所以結果是4/8
8/4? ? ?//&p+1仍然是指針,所以結果是4/8
8/4? ? ?//&p[0]+1表示的是b的地址,所以結果是4/8
- 題目7:下面代碼的運行結果
#include <stdio.h>
#include <string.h>
int main()
{char* p = "abcdef";printf("%zu\n", strlen(p));printf("%zu\n", strlen(p + 1));printf("%zu\n", strlen(*p));printf("%zu\n", strlen(p[0]));printf("%zu\n", strlen(&p));printf("%zu\n", strlen(&p + 1));printf("%zu\n", strlen(&p[0] + 1));return 0;
}
//strlen會往p所指向的那塊內存先后找,直到找到\0,所以結果是6
//p+1指向元素b,所以結果是5
//*p得到a,a的ASCII碼值是97,strlen會把這個數值當成一個指針,但這個地址不一定在當前程序內,也不一定存在,就造成了非法訪問,引起程序崩潰
//p[0]等價于*p,結果與上面一樣
//&p是二級指針,strlen會往&p所指向的那塊內存先后找,直到找到\0,但是不知道\0在那塊空間的位置,所以結果是隨機值
//和上面一樣,結果是隨機值,但是這里有一個問題,strlen(&p)和strlen(&p+1)的結果是否有關系呢?答案是沒有關系,因為你不知道從p往后找是否能在走完p所對應的內存之前找到\0
//&p[0]+1表示b的地址,所以結果是5
- 題目8:下面代碼的運行結果
#include <stdio.h>
int main()
{int a[3][4] = { 0 };printf("%zu\n", sizeof(a));printf("%zu\n", sizeof(a[0][0]));printf("%zu\n", sizeof(a[0]));printf("%zu\n", sizeof(a[0] + 1));printf("%zu\n", sizeof(*(a[0] + 1)));printf("%zu\n", sizeof(a + 1));printf("%zu\n", sizeof(*(a + 1)));printf("%zu\n", sizeof(&a[0] + 1));printf("%zu\n", sizeof(*(&a[0] + 1)));printf("%zu\n", sizeof(*a));printf("%zu\n", sizeof(a[3]));return 0;
}
解析:
? ? ?二維數組是元素為一維數組的數組
48 //a是數組名,數組名單獨放在sizeof()中,計算的是整個數組的大小,所以結果是4*12=48
4? //a[0][0]表示的是數組中的整型元素,所以結果是4
16? //a[0]表示的是第一行一維數組的數組名,數組名單獨放在sizeof中,計算的是整個數組的大小,所以結果是4*4=16
4/8? ?//a[0]表示第一行一維數組的數組名,數組名并沒有單獨放在sizeof中,所以表示的是第一行一維數組首元素的地址,所以a[0]+1表示的是第二個元素的地址,既然是地址,那大小就是4/8
4? ?//a[0]+1表示的是第一行的一維數組中第二個元素的地址,那么*(a[0]+1)就是訪問數組中元素,所以結果為4
4/8? //a表示二維數組的數組名,數組名并沒有單獨放在sizeof中,表示的是第一行一維數組的地址,a+1表示要跳過第一行整個一維數組,指向第二行,即表示第二行一維數組的地址,既然是地址,那大小就是4/8
16? //a+1表示第二行一維數組的地址,*(a+1)表示訪問整個一維數組,既然是訪問整個一維數組,結果自然是4*4=16(也可以這么理解:*(a+1)等價于a[1],a[1]表示的是第二行一維數組的數組名,數組名單獨放在sizeof中,結果就是計算這個一維數組的大小)
4/8? ?//a[0]表示第一行一維數組的數組名,前面有&,取出的是整個一維數組的地址,所以&arr[0]+1表示跳過第一行整個一維數組,指向第二行整個一維數組,既然還是地址,那么結果就是4/8
16? ?//&a[0]+1表示第二行整個一維數組的地址,對它進行解引用,訪問的是整個一維數組,所以結果是16
16? ? //a表示二維數組的數組名,數組名并沒有單獨放在sizeof中,表示的是第一行一維數組的地址,對這個地址進行解引用,訪問的是整個一維數組,所以結果是16(也可以這么理解:*a等價于*(a+0)等價于a[0],表示的是第一行一維數組的數組名,數組名單獨放在sizeof中,訪問的是整個數組,所以結果是16)
16? ?//看到這個代碼,想必很多小伙伴都認為這個包會報錯的,畢竟,這不是越界訪問了嗎?但是,我們說過,sizeof在計算變量大小的時候,是通過類型求推導的,所以他并不會真正去訪問內存,既然沒有越界訪問內存,那不就不會報錯了,而在根據類型推導sizeof(a[3])的大小時,會將a[0]和a[3]是同等類型的,所以結果自然是16了
指針運算試題
- 題目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是數組名,一般情況下表示首元素的地址,a+1表示跳過一個整型的大小,所以a+1指向第二個元素,則*(a+1)得到的是2;&a取出的是整個數組的的地址,所以&a+1會跳過整個整形數組,如圖,將這個地址轉換成整型指針的地址以后賦給ptr,ptr指向如圖所示,ptr-1會向前走過一個整形大小的步長,如圖所示,所以*(ptr-1)得到的是5
- 題目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);printf("%p\n", (unsigned long)p + 0x1);printf("%p\n", (unsigned int*)p + 0x1);return 0;
}
解析:在定義結構體的同時創建了結構體指針變量p,并將0X100000強制類型轉換成結構體指針類型以后賦值給p,p+0x1也就是p+1,p所指向的變量為結構體類型,所以p+1需要跳過20個字節,所以p+1=0x100020,等等,這對嗎?注意咯,這里是十六進制的形式,逢十六進一,所以結果應該是0x100014,而在X86環境下,會將地址的8位都打印出來,且不會省略掉前面的0,所以結果是00100014.
將p強制類型轉換成unsigned long類型以后,里面的值就是一個0x100000,加上一后就是普通的整型相加,所以結果是0X100001,在X86環境下的打印結果是0X100001
將p強制類型轉換成unsigned int*類型以后,對指針+1會跳過4個字節,所以結果就是0X100004,在X86環境下,結果就是00100004
- 題目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;
}
解析:我們先要搞清楚數組里面放了啥,數組里面放的是0,1,2,3,4,5嘛?看著好像是的。注意啦兄弟們,數組里面用括號括起來的是逗號表達式哦,所以數組里面的元素應該是{1,3,5,0,0,0}。
弄清這個以后,我們再來看指針。p是一個整形指針,a[0]是第一行那個一維數組的數組名,一般情況下,他表示首元素的地址,也就是說p里面存放的是第一行一維數組首元素的地址,p[0]等價于*(p+0),p+0還是表示第一行一維數組首元素的地址,對它進行解引用,訪問的就是第一行一維數組的首元素,也就是1,所以打印結果是1.
- 題目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;
}
aa表示的是二維數組的名稱,一般情況下,數組名表示首元素的地址,則aa表示第一行一維數組的地址,aa+1表示跳過整個一維數組,則aa+1指向第二行整個一維數組;&aa取出的是整個二位數組的地址,&aa+1跳過的是整個二維數組,由此,可得圖中各指針指向的由來。
ptr1和ptr2都是整型指針,+1時跳過一個整形元素的步長,-1時向前走一個整形元素的步長,所以得到相應指向,對指針解引用以后可以得到打印結果是:10,5
- 題目5:下面代碼的運行結果
//假設環境是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;
}
圖示解析:
- 題目6:下面代碼的運行結果
#include <stdio.h>
int main()
{char* a[] = { "work","at","alibaba" };char** pa = a;pa++;printf("%s\n", *pa);return 0;
}
圖示解析:
- 題目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;
}
圖示解析:
運行結果: