1. 程式人生 > >強制型別(結構體)轉換NULL-----C指標的黑科技

強制型別(結構體)轉換NULL-----C指標的黑科技

一個頭疼的例子(改寫自Tencent—libco)

#include<stdio.h>
#include<string.h>
typedef struct aa{
    char a;
    int b;
    char o[3];
}a;
int main(void)
{
    a test;
    memset(&test, 0, (long)(((a *)NULL)->o));
}

這個memset想要幹啥???

測試

int main(void)
{
    a test;
    printf("%ld\n",(long)(((a *)NULL)->o));
    printf
("%ld\n",(long)&(((a *)NULL)->b)); printf("%ld",(long)(((a *)NULL)->a)); }

結果是慘痛的。。
這裡寫圖片描述

google+學長的開導

首先我們先看這個結構體型別強制轉換是什麼意思呢?
結構體就是定義了一段記憶體空間,指定這段空間的記憶體佈局,比如

struct c{
int a;
char b;
};

我們將其定義為4bytes+1bytes

解答

NULL就是一個型別為void *,值為0的指標,或者說這個指標指向的地址為0。
那麼經過(aa *),NULL被認為指向一段地址空間,它的分佈是4bytes+4bytes+3bytes。
(此處涉及位元組對齊,不是本文重點,請讀者自行查閱)
而(aa *)NULL->o,因為o是陣列名(地址常量),所以此時得到是一個地址,這個地址應該是NULL+o在結構體中的偏移量(8)即0+8
而&((aa *)NULL->b)同理,只不過這裡b是一個變數名,相當於對NULL+b在結構體偏移量(4)解引用,而後再次取地址,即0+4
而(aa *)NULL->a 和b一樣,只是因為沒有取地址,所以是解引用非法地址,所以出現了段錯誤。

驗證

既然我們猜想是這樣的,那麼將NULL換成其他的常量指標也可以,並且計算結果相同。

int main(void)
{
    a test;
    printf("%ld\n",(long)(((a *)1)->o));
    printf("%ld\n",(long)&(((a *)2)->b));
    printf("%ld\n",(long)&(((a *)3)->a));
}

同理 第一個 是1+8應該是9,第二個應該是2+4,第三個則是3+0
讓我們執行一下,看看結果

這裡寫圖片描述

和我們的預測一致

黑科技有什麼用呢

我們知道,位元組對齊是和硬體架構,編譯器相關的,所以當我們需要編寫可移植的程式,並且需要對一個結構體部分記憶體空間操作
(如本文開頭的例子,很明顯memset的目的就是將ab的記憶體空間初始化為0)
如果我們指定位元組數,那麼可移植性就下降了。
通過這種用法,可以由編譯器去計算偏移量,方便了我們的操作。

感謝

感謝Jung Zhang學長給出的例子和解答,感謝騰訊開源的libco專案以及貢獻了這段程式碼的大牛。