C 語言結構中的指標算術

在 C 語言當中,指標的內容就是記憶體位址,於是我們可以利用指標的算術,計算出某些具有特殊價值的數字,然後進行位址操作,以便定址到我們想要的內容上。

舉例而言,在 Linux 的鏈結串列中,就定義了下列的 offsetof() 巨集函數,可以讓我們計算出一個結構與其欄位間的距離,

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

事實上,offsetof() 這個巨集已經被納入到 ANSI C 的 表頭中,您也可以引用該表頭,而不需要自己實作。

如果我們要從欄位指標反求其母結構位址,則可參考下列 Linux 核心中的原始碼,使用 container_of() 函數

#define container_of(ptr, type, member) ({    \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

這樣的巨集函數要如何使用呢?請參考下列範例。

程式範例:結構中的指標運算

檔案:structptr.c

#include <stdio.h>

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({    \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

typedef struct {
  char name[20];
  int age;
} Person;

// 以 Person 中的 age 欄位為例,說明 container_of() 函數的作法
//  container_of(&p.age, Person, age)
//    typeof( ((Person *)0)->age ) is int
//    int *__mptr = (&p.age); 
//    (Person *) ((char *) __mptr - offsetof(Person, age))

int main() {
  Person p = {
    .name="John",
    .age=40
  };

  size_t offset = offsetof(Person, age);
  printf("offset=0x%x\n", offset);
  printf("offsetof(Person,age)=0x%x\n", offsetof(Person, age));
  printf("&p=%p\n", &p);
  printf("&p.age=%p\n", &p.age);
  printf("p.age=%d\n", *(&p.age));
  printf("&p+1=%p\n", &p+1);
  printf("&((Person*)&p)[1]=%p\n", &((Person*)&p)[1]);
  char *cptr = (char*) &p;
  printf("cptr+1=%p\n", cptr+1);
  printf("cptr+20=%p\n", cptr+20);
  printf("*(int *)(cptr+20)=%d\n", *(int *)(cptr+20));
  int *mptr = (&p.age);
  Person *pptr = (Person *) ((char *) mptr-20);
  printf("*pptr=%p\n", pptr);
//  int **agePtr;
//  agePtr  = (int**) (&p+offset);
//  printf("agePtr=%p\n", agePtr);
//  printf("*(&p+offset)=%d\n", *agePtr);
//  printf("&p+offsetof(Person,age)=%p\n", &p+offset);
//  printf("container_of(&p.age,Person,age)=%p\n",        container_of(&p.age, Person, age));
}

執行結果

D:\cp>gcc structptr.c -o structptr

D:\cp>structptr
offset=0x14
offsetof(Person,age)=0x14
&p=0022FF50
&p.age=0022FF64
p.age=40
&p+1=0022FF68
&((Person*)&p)[1]=0022FF68
cptr+1=0022FF51
cptr+20=0022FF64
*(int *)(cptr+20)=40
*pptr=0022FF50