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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

【软件开发底层知识修炼】二十七 C/C++中的指针与数组是不同的

發布時間:2023/12/10 c/c++ 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【软件开发底层知识修炼】二十七 C/C++中的指针与数组是不同的 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
  • 上幾篇文章學習了ABI-應用程序二進制接口:【軟件開發底層知識修煉】二十六 ABI-應用程序二進制接口 學習總結文章目錄
  • 本篇文章就指針與數組的聯系與區別來學習學習

文章目錄

  • 1 疑問
  • 2 指針與數組是不相等的
  • 3 解決疑問
  • 4 總結

1 疑問

在具體用文字理論來說明指針與數組的區別之前,先看一下下面的代碼例子,這兩個程序輸出的結果是一樣的么?不一樣的話,分別輸出什么?

  • main.c
#include <stdio.h>extern char* g_name;int main() {define_print();printf("main() : %s\n", g_name);return 0; }
  • define.c
#include <stdio.h>char g_name[] = "D.T.Software";void define_print() {printf("define_print() : %s\n", g_name); }

將上述兩個程序放到同一文件夾下進行編譯運行:

  • gcc -g main.c define.c -o test.out
  • .test.out

運行結果如下:

  • 但是如果我把main.c中的extern char* g_name; 換成extern char g_name[]; 的話,程序運行就可以通過,并且可以得到預期的結果。

對于這個結果,我想并不是很多人可以理解的。這個問題放到后面解釋。下面我們先來看看指針與數組的一些基本概念。

2 指針與數組是不相等的

  • 指針
  • 指針的本質就是一個變量,它保存的目標值是一個內存地址。這個內存地址是另一個變量或者不管什么東西的地址
  • 指針運算與 * 操作符配合使用能夠模擬數組的行為
  • 數組
  • 數組是一段連續的內存空間的別名
  • 數組名可看做指向數組第一個元素的常量指針。

在C語言中指針與數組在某些層面是具有等價關系的,注意這里說的是某層面。比如下面的代碼層面,指針與數組的操作就是相等的:

那么,既然我們已經學習了那么多匯編的知識,上面的指針與數組的操作在匯編層面(或者叫做二進制層面)是否相等?我們以實際的例子來說明,編譯下面代碼,并生成匯編代碼,查看test函數的匯編代碼:

#include <stdio.h>int test() {int a[3] = {0};int* p = a;p[0] = 1; // a[0] = 1p[1] = 2; // a[1] = 2a[2] = 3; // p[2] = 3 }int main() {test();return 0; }
  • gcc -g test.c -o test.out
  • objdump -S test.out > test.s 生成test.s反匯編代碼

查看test.s中的test函數中的匯編代碼,如下:

int test() {8048394: 55 push %ebp8048395: 89 e5 mov %esp,%ebp8048397: 83 ec 10 sub $0x10,%espint a[3] = {0}; 804839a: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%ebp) //a[0]的值80483a1: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp)80483a8: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%ebp)int* p = a; //指針p指向數組a的第一個元素,4字節80483af: 8d 45 f0 lea -0x10(%ebp),%eax80483b2: 89 45 fc mov %eax,-0x4(%ebp)p[0] = 1; // a[0] = 1 由于是在第一個位置,沒必要使用add $0x0,%eax80483b5: 8b 45 fc mov -0x4(%ebp),%eax80483b8: c7 00 01 00 00 00 movl $0x1,(%eax)p[1] = 2; // a[1] = 2 可以看出有兩次尋址的過程80483be: 8b 45 fc mov -0x4(%ebp),%eax //首先把指針p存的地址取出來傳給eax寄存器80483c1: 83 c0 04 add $0x4,%eax //然后將eax+480483c4: c7 00 02 00 00 00 movl $0x2,(%eax) //最后將數值2傳給eax寄存器中存的地址所在的內存處,注意這句話的理解。a[2] = 3; // p[2] = 380483ca: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%ebp) //可以看出如果是數組的話,直接將值賦值給對應內存處,而不用像指針那樣進行兩次地址的操作 }80483d1: c9 leave 80483d2: c3 ret

對于上面的匯編代碼,應該并不是很多人都可以理解。不理解也無所謂,能夠看出我們的問題所在即可。

  • 首先看上面,對于p[0]=1; p[1]=2; 這兩段代碼,它們所對應的匯編代碼,由于p[0]比較特殊,所以看p[2]的。上線的注釋也是比較詳細了,由此我們知道如果將指針當做數組來使用,首先需要取出指針所存儲的地址,然后將地址值+4,然后在加了4的地址處賦值,這很明顯是兩次尋址操作。一次是從指針中取出地址,二是根據這個地址再找到相應的內存然后進行賦值。
  • 但是對于 a[2] = 3; 這段話,看上面的匯編代碼,很明顯,就是直接進行一次內存操作。這顯而易見。

由此我們可以粗略的得出以下結論:

  • 指針與數組不管在真么情況下,在二進制層面是完全不同的。盡管在語言書寫的時候等效,但是效率是相差很大的
  • 指針操作是先尋址,然后再對內存單元進行操作
  • 數組是直接對內存單元進行操作
  • 然后就是,在大多數情況下,編譯器做了很多的工作,它讓程序員可以更高效的寫代碼,所以在很多情況中,指針和數組在語言編寫層面,是一樣的,就像上線的示例代碼一樣。

    3 解決疑問

    上一節內容我們學會了指針與數組的一些區別,現在就來看看最開始的疑問,最開始main.c和define.c編譯運行后,為什么會產生錯誤,并且為什么是段錯誤呢?下面就一點點揭開迷霧。

    • 首先我們要知道的前提知識點,C/C++編譯器的天生缺陷
    • C/C++編譯器由4個子部件組成,分別是預處理器,編譯器,匯編器,鏈接器
    • 每個子部件之間獨立工作,相互之間沒有通信
    • 對于語法的檢查與規范只在編譯器(是指第二個子部件的編譯器)編譯階段有效(如:類型約束和保護成員)
    • 編譯器認為,每一個源文件都是相互獨立的,對各個源文件單獨進行編譯(當然最后是需要將各個單獨編譯后的文件進行鏈接的)。這個是導致上面錯誤代碼的直接原因。具體還看下面的分析。

    那么對于上面的幾條知識點,我們使用下面的圖解進行說明:

    • 上面圖示中說了在兩個文件中類型不一致導致運行時錯誤,當然這是表面原因,并且如果是其他的類型(不是指針的類型),有可能就不會出錯。所以我們還需要深挖這其中的錯誤。
    • 針對我們的代碼的話,就是在main.c中將g_name聲明為指針,那么編譯器進行編譯的時候,就是單獨編譯main.c文件,并且將g_name按照指針的方式進行編譯。那么由第二節的內容知道,指針的操作是需要兩次尋址的。 這里我我們先記住,下面的分析會用上。

    為了能夠更加清楚的說清楚問題,下面我們針對上述的main.c與define.c的編譯的過程簡單的用圖表示一下:

    • 上面最后將define.c中的數組g_name的首地址與main.c中代表的指針g_name鏈接起來,具體如何鏈接呢?請看以下圖示:
  • 剛開始define.c中的g_name就是一個數組的首地址,如下圖所示:
  • 當將main.c中的指針g_name與上面的define.c中的g_name進行鏈接后,由于g_name是指針,占4字節,所以鏈接后如下圖:
  • 上面的圖示分析如果能看懂的話,就知道g_name 是一個占有4字節的指針,而g_name 是一個指向數組首地址的值。如果我們注意到前面所說的指針作為數組是需要兩次尋址操作的話,我們就應該知道,如果使用g_name 的話,首先將它存的地址:“D.T.” 取出來,可以看到,它本身應該存的是地址,但是現在是一串字符。然后用這個“地址”來尋址另一個內存地址處。到這里,就明了了,上面的一串字符所代表的地址處是一個未定義的,是一個野地址!!!也就是說在運行的時候,此時g_name是一個野指針!!!這必然會產生段錯誤了!!!

    • 這就是為什么,產生的錯誤是段錯誤。真正的原因歸根結底是野指針的原因。

    對于上面存在的問題,我們盡量使用以下的方法來解決:

    • 盡可能不使用跨文件的全局變量,也就是非static的全局變量
    • 當必須使用時,在統一固定的頭文件中聲明global.h
    • 其他源文件包含上述global.h即可

    4 總結

    • 在進行總結前,這里務必再次將聲明與定義的區別說明一下:
  • 聲明只是告訴編譯器,目標存在,可使用
  • 定義,是為目標分配內存(變量)或確定執行流(函數)
  • 理論上,任何目標都需要先聲明,再使用
  • C/C++允許聲明與定義的統一
  • 下面是針對本文的指針與數組的區別的總結

    • C/C++語言中的指針與數組在某些語言層面上的使用時等價的
    • 指針與數組在二進制層面是完全不等的
    • C/C++編譯器忽略了源碼之間的依賴關系
    • 如果一定要使用跨文件之間的全局變量的話,最好將全局變量放到一個統一的頭文件global.h中
    • 然后其他源文件包含global.h即可

    對于上面的分析,如果沒有懂,可以加左側群,進群進行交流。

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的【软件开发底层知识修炼】二十七 C/C++中的指针与数组是不同的的全部內容,希望文章能夠幫你解決所遇到的問題。

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