2017-09-14 80 views
4

我有兩個文件:爲什麼用C這個簡單的程序崩潰(陣列VS指針)

在文件1.C我有以下的數組:

char p[] = "abcdefg"; 

在文件0.c我有以下代碼:

#include <stdio.h> 

extern char *p; /* declared as 'char p[] = "abcdefg";' in 1.c file */ 

int main() 
{ 
    printf("%c\n", p[3]); /* crash */ 
    return 0; 
} 

這是命令行:

gcc -Wall -Wextra  0.c 1.c 

我知道extern char *p應該已經:extern char p[];,但我只是想解釋爲什麼它不能在這種特殊情況下工作。雖然它在這裏有效:

int main() 
{ 
    char a[] = "abcdefg"; 
    char *p = a; 

    printf("%c\n", p[3]); /* d */ 
    return 0; 
} 
+6

因此,你已經聲明瞭一個具有兩個衝突類型的變量,並詢問它爲什麼不是加工? –

+0

在彙編程序文件('gcc -S -fverbose-asm -O')中的循環k在'1.c''p'中爲 –

+2

是*數組*,在'0.c'中是*指針*。 –

回答

13

你的兩個例子沒有可比性。

在你的第二個例子,你有

char a[] = "abcdefg"; 
char *p = a; 

所以a是一個數組,並p是一個指針。畫在圖片中,它看起來像

 +---+---+---+---+---+---+---+---+ 
    a: | a | b | c | d | e | f | g | \0| 
     +---+---+---+---+---+---+---+---+ 
     ^
     | 
    +----|----+ 
p: | * | 
    +---------+ 

而且這一切都很好;該代碼沒有問題。

但在你的第一個例子中,在文件1.c你定義一個名爲p數組:

+---+---+---+---+---+---+---+---+ 
p: | a | b | c | d | e | f | g | \0| 
    +---+---+---+---+---+---+---+---+ 

可以命名的數組「p」如果你想(編譯當然不關心),但隨後,在文件0.c中,您改變主意並聲明p是一個指針。您還聲明(使用「extern」關鍵字)p是在其他地方定義的。所以編譯器會根據你的要求編寫代碼併發送到位置p,並期望在那裏找到一個指針 - 或者在圖片中,它希望找到一個包含指向其他地方的箭頭的框。但它實際上發現的是你的字符串"abcdefg",只是它沒有意識到它。它可能最終試圖將字節0x61 0x62 0x63 0x64(即,構成字符串"abcdefg"的第一部分的字節)解釋爲指針。顯然這是行不通的。

你可以清楚地看到這一點,如果你改變0.cprintf調用

printf("%p\n", p); 

這將輸出指針p爲指針的價值。 (當然,p並不是一個真正的指針,但是你對編譯器說謊,並且告訴它它是,所以你會看到的是編譯器把它當作指針的結果,再試圖瞭解這裏。)在我的系統這種打印

0x67666564636261 

這是所有8個字節的字符串"abcdefg\0"的,按相反的順序。 (由此我們可以推斷,我這(一)使用64位指針和(b)的機器上是小端)。所以,如果我試圖打印

printf("%c\n", p[3]); 

它會嘗試獲取來自0x67666564636264(即,0x67666564636261 + 3)的字符並打印出來。現在,我的機器具有相當數量的內存,但它沒有那麼多,所以位置0x67666564636264不存在,因此當它試圖從那裏獲取時程序崩潰。

還有兩件事。

如果數組是不一樣的指針,你怎麼會在你的第二個例子說

char *p = a; 

脫身,一個我說的是「所有的罰款;沒有問題」? 如何將右側的數組分配給左側的指針? 答案是著名的「C數組和指針的等價」(臭名昭著的?):實際發生的事情是一樣,如果你說的

char *p = &a[0]; 

每當您在表達式中使用數組,你得到的是實際上是一個指向數組第一個元素的指針,就像我在這個答案中的第一張圖片中展示的一樣。

當你問到「爲什麼它不工作,而它在這裏工作?」時,還有兩種其他的方式可以問你。 假設我們有兩個功能

void print_char_pointer(char *p) 
{ 
    printf("%s\n", p); 
} 

void print_char_array(char a[]) 
{ 
    printf("%s\n", a); 
} 

然後假設我們回到你的第二個例子,有

char a[] = "abcdefg"; 
char *p = a; 

和假設,我們稱之爲

print_char_pointer(a); 

print_char_array(p); 

如果你嘗試它,你會發現它們都沒有問題。 但是,這怎麼可能呢?當我們調用print_char_pointer(a)時,如何將數組傳遞給 需要指針的函數? 當我們調用print_char_array(p)時,我們如何將指針傳遞給需要數組的一個 函數?

嗯,請記住,只要我們在表達式中提到一個數組, 我們得到的是一個指向數組第一個元素的指針。所以,當 我們稱之爲

print_char_pointer(a); 

我們得到的是就像我們寫了

print_char_pointer(&a[0]); 

什麼是真正被傳遞給函數是一個指針,這是 函數需要什麼,所以我們很好。

但是另一種情況下,我們將指針傳遞給聲明爲接受數組的函數?那麼,「C中數組和指針之間的等價」實際上是另一個原則。 當我們寫

void print_char_array(char a[]) 

編譯對待它就像我們寫了

void print_char_array(char *a) 

爲什麼會編譯器做這樣的事情?爲什麼呢,因爲它知道 沒有數組會被傳遞給一個函數,所以它知道沒有 函數實際上會收到一個數組,所以它知道 函數會接收一個指針。所以這就是編譯器對待它的方式。

(而且,很清楚,當我們談到「在C數組和指針的等價 」,我們不是說 指針和數組等價的,只是有這個 特殊等價我已經提到了 這兩個等價原則已經在這裏,其中三個都是 ,僅供參考:(1)當你在表達式中提到一個數組的名字時,你自動獲得的數組的名字是 是一個指向數組的第一個元素的指針 (2)每當你聲明一個似乎接受一個 數組的函數時,它實際接受的是一個指針。 (3)無論何時您在 p[i]的指針上使用「數組」下標運算符[],您實際得到的結果與您寫入*(p + i)的結果一樣。事實上,如果仔細考慮一下,由於 原則(1),即使當您在 上使用數組下標運算符時,您實際上在 指針上使用它。但這是一個非常奇怪的概念,如果你不想, 不必擔心,因爲它只是起作用。)

4

因爲數組不是指針。你告訴程序「其他地方有一個字符指針」,但你實際上沒有 - 你有一個數組。

在表達式中使用時,數組將衰減爲指針,但這並不意味着數組的指針。欲瞭解更多信息,請參閱Is an array name a pointer?

在你的第二個例子中,你有一個數組一個指針,兩個單獨的變量,所以它是一個不同的情況。

4

讓我在反向解釋:

在第二種情況下,你有一個數組,然後將它指向數組的指針。

通過指針訪問涉及間接內存地址(「打印該指針指向的第三個字節」與「打印該數組的第三個字節」)。

在第一種情況下,你在其他地方有一個數組,但告訴編譯器你在那個地方有一個指針。所以它會嘗試讀取指針並從指向的地方讀取數據。但沒有指針 - 即時有數據,所以指針指向「任何地方和任何地方」(至少很可能)。這構成未定義的行爲(通常縮寫爲UB)。