天天看點

自定義宏實作sizeof,解決多年來對利用0位址計算的疑惑

  sizeof是C語言的一個運算符,對,簡簡單單就隻是一個運算符,不是什麼宏,不是什麼函數,就像你經常見到的加減乘除符号一樣,低調得不能再低調的這麼一個運算符。是以好心勸你别去找sizeof的源碼啦😂(比如我:C語言sizeof源碼實作、sizeof實作機制、核心sizeof實作。。。)

  或許你一樣好奇,對于一個整形數,可以擷取到它為4位元組,不足為怪,但是對于一個數組,也就是記憶體,它是怎麼知道要讀多長呢?字元串還好,有結束标志符,但是數組沒有啊。注意,sizeof在預處理階段并沒有展開,我認為是與編譯器的某些内部操作有關,因為不管你怎麼搞,編譯器對你是最清楚的,你用了任意一塊記憶體都被編譯器記錄在案,是以還是有在gcc編譯裡有哪些操作我們看不到的,是以還是别去想sizeof到底怎麼實作的啦。

  寫代碼的人應該想着自己來實作這麼一個功能,即使用宏的方式實作一個與sizeof差不多的效果,這裡隻涉及sizeof簡單的用法,比如sizeof(int),sizeof(3),别整那些sizeof(1+2)或者sizeof int什麼的😅

  網傳較多的有兩種方法:

//針對T為一個類型名的情況
#define _sizeof_type(T) (size_t)((T*)0 + 1)

//針對T為一個變量或者數組名的情況
#define _sizeof(T) ((size_t)(&T + 1) - (size_t)(&T))
           

  利用0位址來計算內插補點,好處多多。對于第一種宏,隻能用于計算類型名,比如int,char,float,如果是對于某種類型的變量名則不行,實作原理就是将傳入的類型名構造一個指針類型,傳入int,那就構造int * 指針類型。

  已知指針移動的步長與它的類型有關(比如int * 指針加1就移動4位元組,char * 加1就移動1位元組),那麼就可以利用這個特性來計算,比如對宏裡構造的指針進行加1,那麼實際就會在原位址上加4位元組的位址偏移。我們最終需要的是加1前和加1後的位址偏移量,也就是說那是一個內插補點,內插補點的話就與原位址沒什麼關系咯。

  比如我将0位址轉換成在宏裡的構造類型,比如剛傳入宏的是int,那麼就把0位址轉換成int * 類型,此時的0位址非彼時的0位址,此時的0位址加1,就會跳到第4哥位址去了,因為它被賦予了int * 指針的特性。同理,如果把0位址轉成char * 類型,加1的話步長就為1。這個轉換由(T * )0實作。

  為什麼用0呢,用0計算偏移的時候,減數部分就為0啦,是以計算內插補點就很友善。讓具有某種特性的0位址加1,然後用加1之後的位址減去原來的位址值(其實就是減0),得到的就是偏移的值了。比如對int * 類型的0位址加1,那麼加1後的位址值就為0x04了,偏移值就是(0x04-0x00),是以((T*)0 + 1)得到的就是偏移值,但是這個值是位址類型的,比如0x04,這不是我們想要的,是以需要将它轉換為整形數,加個size_t強制轉換,那麼0x04就是整形4了,也就是得到了int類型占用的位元組數。

 現在舉一個不用0位址的例子來打消你最後一點疑慮。同樣還是使用上面第一種宏的情況:

  不适用0位址時,比如使用4位址,那麼就有:

  

((T*)4+1)

就是在位址4的基礎上加1,比如T為int時((T*)4+1)得到值是0x08,size_t轉換一下得到的就是整形8了。

  

((T*)4)

則是位址4的位址了,比如0x04,size_t轉換一下得到的就是整形4了。

  是以

( (size_t)((T*)4+1) - (size_t)((T*)4))

得到的就是 8-4=4啦。是以說,用0位址就很友善,簡簡單單的隻是利用0位址參與一下運算,得到偏移值,實際上并沒有對0位址進行什麼樣的操作,因為那是不合法的。

  再看第二種宏,不能對類型進行操作,使能對某種類型的變量名操作

  原理也一樣,相比于第一種利用類型名稱進行構造指針,這一次的是利用變量名直接擷取到該位址,當擷取到該位址時,該位址就已經具有它資料類型所對應的特性了。比如對于一個int a,&a的位址(即指針)加1,那麼它會在原位址基礎上移動4位元組,如果是char型就移動1位元組。

  

  是以,這個很容易了解,就是求內插補點。

  

  

  

  下面就是第三種實作sizeof的方法,基于一個例子我所想到的一個方法,先看例子,寫一個标準宏 MIN ,這個宏輸入兩個參數并傳回較小的一個。

  這不難了解,就是一個三目運算符,但是對于不同類型變量比較時就不太好了,然後就有以下的實作:

#define MIN(x,y) do{ \
	typeof(x) _x = (x); \
	typeof(y) _y = (y); \
	(void) (&_x == &_y); \
	printf("a 和 b 最小的: %d\n",_x <= _y ? _x : _y); \
	}while(0)
           

  主要看typeof,typeof(x) _x作用:定義的_x類型與x的相同,比如

int a=10;
typeof(a) b; //b的類型為int
typeof(int) b; //b的類型為int
           

  聰明的你應該想到了第三種實作sizeof的方法,對,就這😂(此時請不要再叫我宏實作typeof,謝謝)

#define _mysizeof(T) (size_t)((typeof(T)*)0 + 1)

//_mysizeof(int)展開為(size_t)((int *)0 + 1)
/*
	char a;
	//_mysizeof(a)展開為(size_t)((char *)0 + 1)
*/
           

  相比于前面兩種方法,這種方法整合了它們,就是你傳入的參數不管是資料類型名還是變量名,我都接收,并且把它構造成對應類型的指針類型,剩下的實作原理和前面兩種的一樣啦。

自定義宏實作sizeof,解決多年來對利用0位址計算的疑惑

  

  

  如果覺得還不夠,那就進一步再看看對于利用0位址計算的例子,這裡是連結:linux核心雙連結清單實作快速排序

  

  下面是主要部分截圖

  

自定義宏實作sizeof,解決多年來對利用0位址計算的疑惑

  

  如有錯誤,歡迎指正,謝謝!

  

  

繼續閱讀