日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【精华文】C语言结构体特殊情况分析:结构体指针 / 基本数据类型指针,指向其他结构体

發布時間:2023/11/30 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【精华文】C语言结构体特殊情况分析:结构体指针 / 基本数据类型指针,指向其他结构体 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

參考鏈接:Structure pointer pointing to different structure instance
注:可以查看此篇的問題和唯一的回復,那是相對正確的,不要看comment,有很多錯誤。

我是拒絕分析這種問題的,因為似乎沒有人會這么亂用,但是……在華保健老師的編譯原理示例代碼和Linux0.11內核中,就遇到了這么神奇的代碼,那就不得不研究一下了!畢竟是大神寫的代碼,我不知道應該是我渣。

1 測試代碼

#include <stdio.h> #include <stdlib.h>struct A {char a;int b; };struct B {int c;int d; };struct C {int e;char f; };int main() {struct A a = { 'a', 100 };struct B b = { 101, 300 };struct C c = { 200,'c' };// 根據字節對齊,都占據8字節printf("A: size %d %c %d\n", sizeof(a), a.a, a.b);printf("B: size %d %d %d\n", sizeof(b), b.c, b.d);printf("C: size %d %d %c\n", sizeof(c), c.e, c.f);struct A *ap = &b; // A結構體指針,指向結構體Bprintf("%d %d\n",ap->a, ap->b);printf("%c %d\n", ap->a, ap->b);char *chp = &b;chp[1] = 'b'; // 這塊區域其實是字節對齊導致的空閑空間printf("%d %d\n", ap->a, ap->b);printf("%c %d\n", ap->a, ap->b);/* 如何訪問這塊內存,取決于ap指針,能訪問多大地方,取決于內存區域本身 */ap->a = 'c'; // ap->a = 'c'就是相當于 char a = 'c';ap->a = 1000; // ap->a = 1000 就是相當于 char a = 1000; 1000過大會被截斷高位ap->b = 3000; // ap->b <=> int b ...struct C *cp = &b; // C結構體指針,指向結構體Bprintf("%d %d\n", cp->e, cp->f);printf("%d %c\n", cp->e, cp->f);cp->e = 3000;cp->f = 'e';cp->f = 1000;// 整形指針指向結構體Aint *bp = &a;bp[0] = 1000;bp[1] = 2000;printf("A: %c %d\n", a.a, a.b);printf("A: %d %d\n", a.a, a.b);bp[2] = 2000; // 可以修改內存,但是堆棧溢出,// 因為該空間沒有被分配(局部變量是保存在堆棧中的)return 0; }

2 結構體占據空間問題 & 字節對齊

struct A {char a;int b; };struct B {int c;int d; };struct C {int e;char f; };... struct A a = { 'a', 100 }; struct B b = { 101, 300 }; struct C c = { 200,'c' };// 根據字節對齊,都占據8字節 printf("A: size %d %c %d\n", sizeof(a), a.a, a.b); printf("B: size %d %d %d\n", sizeof(b), b.c, b.d); printf("C: size %d %d %c\n", sizeof(c), c.e, c.f); ...

運行以上程序,我們可以直到,三個結構體分別創建了一個變量,并且每個結構體占據的空間大小都是8字節。


至于為什么都是8字節,這是內存對齊問題,不展開說明了,我們看看這幾個結構體被分配的空間情況吧。

  • 每個結構體都占8字節的內存空間
  • 紅色部分表示實際占用的空間
  • 藍色部分表示空閑空間

注意:這就意味著,凡是被分配的8字節空間,是可以任意訪問的,而空間外面是不允許訪問的。

讓結構體A的指針ap,指向結構體B的變量b

現在我們建立一個結構體A的指針,讓其指向b。

struct A *ap = &b; // A結構體指針,指向結構體B printf("%d %d\n",ap->a, ap->b); printf("%c %d\n", ap->a, ap->b);


我們看看內存的情況,再分析一下打印的結果。

上面是內存的分布情況,現在

  • 訪問ap->a打印出來的是:101,e
  • 訪問ap->b打印出來的是300

所以ap指針實際訪問的應該是下面重點標出的部分:

而這部分,是不是很熟悉?

所以,ap指針盡管指向了結構體B,但是實際還是按照結構體A的結構訪問內存的。

2.1 使用char指針指向結構體B

剛才我們發現,使用結構體A的指針,可以直接訪問結構體B,那么,如果是基本數據類型呢?我們試一下。

char *chp = &b; chp[1] = 'b'; // 這塊區域其實是字節對齊導致的空閑空間 printf("%d %d\n", ap->a, ap->b); printf("%c %d\n", ap->a, ap->b);


我們看到內存分布如上圖,現在執行chp[1] = 'b'(b的ASCII碼是62)

之后就變成了:

哦!這是令人驚訝的,char類型的指針指向了一塊內存區域,然后使用下標修改了內存的值!

還記得動態數組申請嗎?和內個是一樣的原理!

int *a = (int *)malloc(sizeof(int) * 10); a[0] = 1; // 使用下標訪問 a[1] = 2; ... free(a);

告訴我們兩件事

  • 指針默認指向最開始的元素,索引是0
  • 使用下標索引可以依次訪問后面的元素,每次向后移動的內存數,取決于指針的數據類型
  • 所以上面的事情不難理解。

    然后我們繼續執行程序

    printf("%d %d\n", ap->a, ap->b); printf("%c %d\n", ap->a, ap->b);

    盡管之前的空閑空間改變了,但是結果依然不變,也就是說我們之前的說法是正確的。

    再進一步驗證

    /* 如何訪問這塊內存,取決于ap指針,能訪問多大地方,取決于內存區域本身 */ ap->a = 'c'; // ap->a = 'c'就是相當于 char a = 'c'; ap->a = 1000; // ap->a = 1000 就是相當于 char a = 1000; 1000過大會被截斷高位 ap->b = 3000; // ap->b <=> int b ...

    結果顯而易見,對于ap->a = 1000,盡管1000已經超過了1字節大小,但是最終只修改了第一個字節,這就好比char a = 1000一樣,a = 0xe8

    是的,1000 = 0x3e8,但是只有一個字節,所以最高位的3被舍棄了。

    2.2 用結構體C指針cp指向結構體B

    struct C *cp = &b; // C結構體指針,指向結構體B printf("%d %d\n", cp->e, cp->f); printf("%d %c\n", cp->e, cp->f);cp->e = 3000; cp->f = 'e'; cp->f = 1000;

    我們再試一試!

    最終結果顯而易見。


    2.3 用int指針指向結構體A

    // 整形指針指向結構體A int *bp = &a; bp[0] = 1000; bp[1] = 2000; printf("A: %c %d\n", a.a, a.b); printf("A: %d %d\n", a.a, a.b); bp[2] = 2000; // 可以修改內存,但是堆棧溢出,// 因為該空間沒有被分配(局部變量是保存在堆棧中的)

    其實這個事情我們之前干過了,之前用char,現在用int再干一下。

    這個事情進一步說明了什么呢?

  • a提供了有限的8字節內存空間
  • bp指針能夠修改哪里,取決于它指向的地址;一次修改多大空間,取決于它數據類型的大小
  • 指針不能修改未被分配的空間,最后bp[2]訪問了外界空間,因此產生了
  • 因為局部變量都是被分配在棧中的,現在這個局部變量訪問越界了,產生了錯誤,棧被破壞

    棧破壞這里情況非常復雜,先粗淺理解為,使用了未分配的空間導致了錯誤吧。

    Linux0.11 內核中,使用上述方法,實現了GDT和IDT。

    3 小結:精華在這里

    分析了這么多,最終小結一下吧。

    我們的眼中只有兩件事

    • 已分配的內存空間
    • 某數據類型的指針

    現在,我們就讓指針指向內存空間的起始地址,然后就可以操作這個內存空間了。

    再增加一些限制

    • 內存空間就這么大,不能訪問外面
    • 指針每次訪問的地址,是通過下標訪問的,一次只能移動數據類型大小的整數倍

    這個時候你眼中的C語言,分配一塊內存,再創建一個指針,打遍天下無敵手!

    當然了,除了特殊情況一般沒人這么干,你會瘋掉,看你代碼的人也會瘋掉!

    4 補充:直接深入底層,看匯編代碼

    之前我們的分析是基于C語言層級的,比較抽象,實際上,編譯完成之后的匯編語言,一看就明白了。

    你可以看到ap->a直接訪問的是byte,而ap->b訪問的是dword,一個是字節,一個是雙字,大小自然清晰。

    這也是編譯器的功能,把C語言提供的,方便人類使用的大量抽象,給翻譯成方便機器使用的少量指令的復雜排列組合。

    5 什么叫打遍天下無敵手呢?

    其實就是瞎玩兒吧……但是的確可以這么干的!我們試一試。

    int main() {char aaa[4] = { 1,2,3,4 };char aaa2[4] = { 1,2,3,4 };int *bbb = &aaa;printf("\n\n%x\n\n\n", bbb[0]);return 0; }

    會打印什么呢?顯而易見的!內存是01 02 03 04,然后一個int *指針訪問了它,打印04030201。

    我們可以使用bbb[0]或者*b都行,因為b指向起始地址。

    那,能不能通過bbb[1]訪問aaa2的內存呢?

    不行! 因為aaa1和aaa2是兩個數組變量,他們在內存中的位置不是連續的,是隨機的,如果你想達到內種效果,那就是前面提到的結構體了,把這兩個放進一個結構體里面,就是連續分配內存了,就能使用bbb[1]了。


    最后,記住只有兩件事

    • 一塊已分配的內存
    • 一個指針

    總結

    以上是生活随笔為你收集整理的【精华文】C语言结构体特殊情况分析:结构体指针 / 基本数据类型指针,指向其他结构体的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。