2014-09-04 54 views
6

有人寫了下面的C程序,並問爲什麼gcc允許「更改數組的基地址」。他知道代碼很糟糕,但仍然想知道。我發現這個問題足夠有趣,因爲C中數組和指針之間的關係很微妙(請看陣列上的地址操作符的使用!「爲什麼有人會這麼做?」),因此容易混淆,因此經常被誤解。這個問題被刪除了,但我想我會再次提出這個問題,並且有適當的背景和 - 我希望 - 正確地回答它。這是原始編程。我們可以通過使用蠻力的數組指針來更改數組的基地址嗎?

static char* abc = "kj"; 

void fn(char**s) 
{ 
    *s = abc; 
} 

int main() 
{ 
    char str[256]; 
    fn(&str); 
} 

它編譯與gcc(帶警告),鏈接和運行。這裏發生了什麼?我們可以通過獲取地址來更改數組的基地址,將它轉換爲指向指針的指針(在所有數組幾乎都是C中的指針之後,是不是它們)並將其分配給它?

+0

爲後人,[這裏](http://stackoverflow.com/questions/25660493/assigning-to-a-char-array-using-assignment-operator-and-double-pointers)是刪除的問題 – 2014-09-04 11:34:36

回答

4

該程序不會更改該陣列的「基地址」。它甚至沒有嘗試。

你傳遞給fn的是內存中256個字符塊的地址。它在數字上與str在其他表達式中衰減的指針相同,只是不同的類型。在這裏,數組真的保持一個數組 - 將地址運算符應用到數組是數組不會衰減到指針的一個實例。例如,增量&str會使其數值增加256.這對於多維數組非常重要,正如我們所知,它實際上是C中的一維數組陣列。增加「二維」數組的第一個索引必須將地址提前到下一個「塊」或「行」的開始處。

現在趕上。就fn而言,您通過的地址指向包含另一地址的位置。那是不正確的;它指向一系列字符。打印該字節序列解釋爲指針會顯示'A'字節值,即65或0x41。然而,fn認爲指向的內存包含一個地址,並用「kj」駐留在內存中的地址覆蓋它。由於在str中分配了足夠的內存來保存地址,因此分配成功並在該位置生成可用地址。

應該指出,這當然不能保證工作。導致失敗的最常見原因應該是對齊問題 - 我認爲str不需要爲指針值正確對齊。標準規定函數參數必須與參數聲明兼容。任意指針類型不能分配給對方(需要通過void指針或者強制轉換)。

編輯: david.pfx指出(即使有適當的轉換),代碼調用未定義的行爲。該標準要求通過最新公共草案第6.5/7節中兼容的左值(包括引用的指針)來訪問對象。當正確編譯和編譯gcc -fstrict-aliasing -Wstrict-aliasing=2 ... gcc警告「類型雙關」。理由是編譯器應該自由地假設不兼容的指針不會修改相同的內存區域;這裏不需要假定fn改變str的內容。這使得編譯器能夠優化不必要的重新加載(例如從內存到註冊)。這將對優化起作用;一個調試會話將無法重現錯誤的可能示例(即,如果正在調試的程序將被編譯而不進行用於調試目的的優化)。話雖如此,如果一個非優化編譯器會在這裏產生意想不到的結果,我會感到驚訝,所以我讓剩下的答案保持原樣。 -

我插入了一些debug printfs來說明發生了什麼。現場可以看到一個實例:http://ideone.com/aL407L

#include<stdio.h> 
#include<string.h> 
static char* abc = "kj"; 

// Helper function to print the first bytes a char pointer points to 
void printBytes(const char *const caption, const char *const ptr) 
{ 
    int i=0; 
    printf("%s: {", caption); 
    for(i=0; i<sizeof(char *)-1; ++i) 
    { 
     printf("0x%x,", ptr[i]); 
    } 
    printf("0x%x ...}\n", ptr[sizeof(char *)-1]); 
} 

// What exactly does this function do? 
void fn(char**s) { 
    printf("Inside fn: Argument value is %p\n", s); 
    printBytes("Inside fn: Bytes at address above are", (char *)s); 

    // This throws. *s is not a valid address. 
    // printf("contents: ->%s<-\n", *s); 

    *s = abc; 
    printf("Inside fn: Bytes at address above after assignment\n"); 
    printBytes("   (should be address of \"kj\")", (char *)s); 

    // Now *s holds a valid address (that of "kj"). 
    printf("Inside fn: Printing *s as string (should be kj): ->%s<-\n", *s); 

} 


int main() { 
    char str[256]; 

    printf("size of ptr: %zu\n", sizeof(void *)); 
    strcpy(str, "AAAAAAAA"); // 9 defined bytes 

    printf("addr of \"kj\": %p\n", abc); 
    printf("str addr: %p (%p)\n", &str, str); 
    printBytes("str contents before fn", str); 

    printf("------------------------------\n"); 
    // Paramter type does not match! Illegal code 
    // (6.5.16.1 of the latest public draft; incompatible 
    // types for assignment). 
    fn(&str); 

    printf("------------------------------\n"); 

    printBytes("str contents after fn (i.e. abc -- note byte order!): ", str); 
    printf("str addr after fn -- still the same! --: %p (%p)\n", &str, str); 

    return 0; 
} 
+2

我想知道在6分鐘前詢問問題時,你是如何及時回答這麼長的問題的,而且你的答案也是在6分鐘前出現的?只輸入程序代碼將花費1分多鐘。 – 2014-09-04 11:12:55

+1

那麼,Ctrl-V需要1/100 ;-)。我在這裏發現,當我提出一個問題時,SO讓我有機會立即與問題一起寫出答案。這種自我Q/A模式是SO的支持格式之一,因此人們可以分享他們的見解(我認爲,如果沒有問題,你就不能寫出答案)。這是我第一次嘗試它。 – 2014-09-04 11:20:37

+0

@VladfromMoscow這是從另一個已被刪除的問題複製粘貼 – 2014-09-04 11:21:48

5

它不能工作(即使在理論上),因爲陣列不是指針:


  • int arr[10]

    使用的內存
    • 量爲sizeof(int)*10字節

    • arr&arr的值不必相同

    • arr指向一個有效的存儲器地址,但不能被設置爲指向另一存儲器地址


  • int* ptr = malloc(sizeof(int)*10)

    使用
    • 金額的存儲器是sizeof(int*) + sizeof(int)*10字節

    • ptr值和&ptr不必相同(實際上,它們大多是不同的)

    • ptr可以被設置爲指向兩個有效和無效的內存地址,儘可能多的次數,你會

+0

那麼,O-OP(不是我)很困惑的事實,你可以採取數組的地址,解除引用和分配給(在有效地鑄造之後)。他預計這會像「(*(&x))= 1」一樣改變「價值」,就像其他指針一樣。由於這個「價值」是他認爲他改變了數組,所以不知何故(不是它的內容)。在我的回答中,我剖析了應用於數組的地址運算符的效果,類型轉換後對'fn'中結果參數值的解釋以及對所涉及值的影響。 – 2014-09-04 11:28:07

+0

@PeterSchneider:你讓我在這裏感到困惑。我給你+1,因爲我發現這個問題很有趣。然後我注意到你也是回答它的人(從第一個評論到答案),這有點奇怪,我不得不說,但就我而言,這沒問題......無論如何,現在你說「O-OP(不是我)感到困惑」。 「O-OP」是什麼意思,你爲什麼稱自​​己爲「不是我」? – 2014-09-04 11:57:35

+0

我用「回答你自己的問題」的格式來傳播智慧;-)。這並不罕見;實際上,它提供了一個選項,用於編輯您的答案以及直到今天我還沒有意識到的問題。這使得OP自己的答案首先出現 - 正如我在問題中所解釋的那樣,它是基於不同用戶詢問然後刪除的答案。那個人應該是「原始海報」或O-OP。這不是我。我是這個主題中的OP。 – 2014-09-04 12:17:21

2

你在這裏只是未定義的行爲。

該函數的參數被聲明爲pointer-to-pointer-to-char。傳遞給它的參數是指向256-char-array的指針。該標準允許一個指針與另一個指針之間的轉換,但由於指向的對象不是指向char的指針,因此對指針的解引用是Undefined Behavior。

n1570 S6.5.3.2/4:

如果無效值已被分配給所述指針,一元的行爲*運算是 未定義。

推測未定義行爲將如何在不同的實現上發揮作用是徒勞的。這顯然是錯誤的。


只要是明確的,則UB是在這條線:

*s=abc; 

指針s沒有指向的正確類型(char*)的目的,因此使用的*是UB 。

+0

我不認爲這是UB(好吧,gcc讓我們通過錯誤的指針類型,嚴格地說它會需要一個強制轉換,但是讓我們在調用'fn()'時假設一個強制類型)。通過的值不是無效的;這是一個有效的數組指針。如果滿足對齊要求,則解除引用是明確的。 Cf n1570,6.3.2.3/7:「指向對象類型的指針可能[顯式地,P.S.]轉換爲指向不同對象類型的指針。」然後該段落討論對齊問題和轉換爲char *時的行爲,但不限制目標指針類型。 – 2014-09-04 12:11:15

+0

還要注意,代碼從不取消引用'* s'(例如嘗試讀取'** s'),這至少在將'abc'分配給'* s'之前,這對於具有存儲器保護的系統是非法的和崩潰。 – 2014-09-04 12:20:36

+1

@PeterSchneider:'* s = abc'根據標準是UB。不管你稱之爲「解除引用」還是不行(我這樣做)。請參閱編輯。 – 2014-09-04 13:37:58

相關問題