【精華文】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打印出來的是:101e
  • 訪問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);

告訴我們兩件事

  1. 指針默認指向最開始的元素,索引是0
  2. 使用下標索引可以依次訪問后面的元素,每次向后移動的內存數,取決于指針的數據類型

所以上面的事情不難理解。

然后我們繼續執行程序

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再干一下。

在這里插入圖片描述

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

  1. a提供了有限的8字節內存空間
  2. bp指針能夠修改哪里,取決于它指向的地址;一次修改多大空間,取決于它數據類型的大小
  3. 指針不能修改未被分配的空間,最后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的內存呢?

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


最后,記住只有兩件事

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384608.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384608.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384608.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

enum in c language

今天說說C語言中的枚舉。 參考&#xff1a;Enumeration (or enum) in C 1 定義 定義一個枚舉類型很容易&#xff1a; enum aa { a1, a2, a3 };這里 enum是關鍵字aa是枚舉變量&#xff0c;也就是我們自定義類型a1,a2,a3是枚舉成員 然后怎么使用呢&#xff1f; 首先&#…

信號量SIGCHLD的使用,如何讓父進程得知子進程執行結束,如何讓父進程區分多個子進程的結束

本教程基于 Ubuntu 20.10 gcc 10.2.0. 示例程序如果不能正常編譯和執行&#xff0c;說明您系統和工具版本與我的不匹配&#xff0c;請自行查閱資料。 0 概述 先給出該信號的描述&#xff1a; SignalValueDescriptionSIGCHLD17Child status has changed (POSIX). Signal sent …

使用gdb調試多進程程序、同時調試父進程和子進程

參考: [1] GDB debugging multi-process programs [2] Debugging programs with multiple processes 根據這兩篇參考鏈接&#xff0c;完全可以實現使用gdb同時調試父進程和子進程。 接下來說明一下可能遇到的坑 gdb8.1版本有bug&#xff0c;設置完set detach-fork-on off&…

Linux安裝Ncurses庫

參考&#xff1a;How To Install Ncurses Library In Linux 針對Ubuntu說明一下&#xff1a; wget https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.2.tar.gz&#xff0c;至于最新版本&#xff0c;自己看官網&#xff0c;修改一下版本號即可。tar xzf ncurses-6.2.tar.gzcd nc…

gdb 10.2的安裝

參考 [1] GDB-10.2 [2] README for GDB release 個人系統 Ubuntu20.10。 注意gdb10.2需要c11語法&#xff0c;需要安裝g 下載安裝包wget https://ftp.gnu.org/gnu/gdb/gdb-10.2.tar.xz解壓縮tar -xvzf gdb-10.2.tar.xz進入解壓之后的目錄mkdir buildcd build配置&#xff0c;…

gdb tui的使用

[1] GDB Text User Interface [2] GDB Text User Interface 簡單來說&#xff0c;進入gdb之后&#xff0c;使用ctrl x 2就足夠了。其他細節請參考上述鏈接&#xff0c;選一個就可以。

C語言中信號函數(signal)的使用

先來簡單談談C語言中的信號&#xff08;signal&#xff09; 首先&#xff0c;signal是C語言庫中的函數&#xff0c;它實際上是軟中斷&#xff0c;也就是軟件發出的終端&#xff0c;本質來說&#xff0c;類似于int n。 對于接收到該軟中斷信號的進程&#xff0c;就會停下手頭的…

UNIX哲學

參考&#xff1a; 對比Linux與Windows 使用Linux想要做某些事情的時候&#xff0c;就拆開想&#xff0c;想想我需要哪些功能&#xff0c;需要哪些工具&#xff0c;依次怎么執行&#xff0c;然后用管道建立連接&#xff0c;讓數據依次流過不同的工具&#xff0c;從而得到最終結果…

fork創建多個子進程

references: [1] how to create two processes from a single Parent [2] fork() in C [3] linux中fork同時創建多個子進程的方法 fork的本質&#xff0c;就是復制&#xff0c;把當前進程復制一份&#xff0c;然后兩個進程并發地執行fork后面的語句&#xff0c;區別就是&#x…

wait系統調用

reference:Wait System Call in C 只強調幾點&#xff0c;剩下的直接看參考鏈接內容就好了&#xff0c;不是偷懶&#xff0c;而是里面內容寫的很好了&#xff0c;沒必要再寫一遍了&#xff0c;這種東西就是單純的系統調用而已&#xff0c;理解了功能&#xff0c;就完事了&#…

Linux進程間通信:共享內存與管道

references: [1] IPC through shared memory [2] Inter Process Communication (IPC) [3] https://www.geeksforgeeks.org/pipe-system-call/ [4] watch command in Linux with Examples 參考鏈接1和2是介紹了共享內存IPC的簡單原理和相關系統調用的使用參考鏈接3是介紹了管道通…

find command基本使用

find命令通常用于根據文件名查找文件&#xff0c;這是最基本用法。 find [path] -name/-iname [filename] path寫要查找的路徑&#xff0c;自動遞歸查找filename寫文件名&#xff0c;可以使用通配符*還有其他什么的表達式 具體細節請man find查閱文檔。

正則表達式特別需要注意的點:“空“字符的匹配

在正則表達式中&#xff0c;[...]代表1個字符&#xff0c;不管里面有多少字符&#xff0c;最終這個東西的結果都是1個字符。 對于表達式[^a]表達的匹配除了a之外的字符&#xff0c;并且是1個字符。 需要注意的是&#xff0c;有些特殊字符是不會被匹配的。 我們看一個示例&am…

vim多列操作--插入/刪除

插入 How to insert text at beginning of a multi-line selection in vi/VimVim Commands 刪除 ctrl v使用上下左右鍵選中一片區域按d刪除

vim進行行內某部分的復制剪切粘貼

ctrl v使用方向鍵選中你要復制的部分 按d&#xff08;剪切&#xff09;或者按y&#xff08;復制&#xff09;再移動到你的目標位置&#xff0c;按p粘貼&#xff08;在正常模式下才行&#xff0c;如果不是&#xff0c;先按esc&#xff09; 這個過程與你操作word文檔的復制粘貼…

函數調用堆棧

基于孟寧老師的Linux內核分析 1 int g(int x){ 2 int y x 3;3 return y;4 }5 6 int f(int x){7 int z x 10;8 return g(z);9 }10 11 int main(){12 int a f(8) 1;13 return 0;14…

gdb調試的幾點提示(1)

GDB debugger Examining Memory Continuing and Stepping How to translate a virtual memory address to a physical address? s和n是C語言的下一步 si和ni是匯編語言下一步 gdb能夠查看的都是虛擬地址&#xff0c;不能查看物理地址&#xff0c;應用程序都不能查看物理地址…

C語言讀取文件

C語言一次性讀取文件 C - File I/O C library function - fread() 需要注意的點 fgets函數&#xff0c;一次只能讀取一行&#xff0c;并且在結尾自動添加\0fread函數&#xff0c;可以讀取很多內容&#xff0c;但是不會添加\0需要手動完成&#xff0c;具體看[參考1]

Vivado提高綜合和實現的速度

讓計算機的資源盡可能給vivado&#xff0c;綜合、實現的時候修改一個參數 jobs改為你的計算機的最大值&#xff0c;我的計算機是12核的。 速度會快很多&#xff01;

安裝Ubuntu RISC V toolchain失敗(網速、git配置原因)

git獲取大容量工程出錯&#xff1a;RPC failed&#xff1b; curl GnuTLS recv error : Decryption has failed. error: RPC failed; curl 56 GnuTLS recv error (-54): Error in the pull function.fatal: The remote end 官方GitHub倉庫 gitee鏡像倉庫 如果網速不夠&#xff0…