簡體   English   中英

返回包含數組的結構

[英]Returning struct containing array

以下是gcc 4.4.4下的簡單代碼段錯誤

#include<stdio.h>

typedef struct Foo Foo;
struct Foo {
    char f[25];
};

Foo foo(){
    Foo f = {"Hello, World!"};
    return f;
}

int main(){
    printf("%s\n", foo().f);
}

將最后一行更改為

 Foo f = foo(); printf("%s\n", f.f);

工作正常。 使用-std=c99編譯時,這兩個版本都可以工作。 我是在簡單地調用未定義的行為,還是在標准中進行了某些更改,從而使代碼可以在C99下工作? 為什么在C89下崩潰?

我相信C89 / C90和C99中的行為均未定義。

foo().f是數組類型的表達式,特別是char[25] C99 6.3.2.1p3說:

除非它是sizeof運算符或一元運算符的操作數,或者是用於初始化數組的字符串文字,否則將類型為“ array of type ”的表達式轉換為類型為“ pointer to type ”的表達式,指向數組對象的初始元素,並且不是左值。 如果數組對象具有寄存器存儲類,則該行為是不確定的。

在這種特殊情況下(作為函數返回的結構元素的數組)的問題是沒有“數組對象”。 函數結果按值返回,因此調用foo()的結果是struct Foo類型的 ,而foo().fchar[25]類型的值(不是左值)。

據我所知,這是C語言(最多C99)中唯一可以使用數組類型的非左值表達式的情況。 我想說的是,嘗試訪問它的行為並沒有被遺漏所定義,這可能是因為該標准的作者(可以理解的恕我直言)沒有想到這種情況。 在不同的優化設置下,您可能會看到不同的行為。

新的2011 C標准通過發明新的存儲類來修補這種情況。 N1570 (鏈接到最新的C11草案)在6.2.4p8中說:

具有結構或聯合類型的非左值表達式,其中結構或聯合包含具有數組類型的成員(遞歸包括所有包含的結構和聯合的成員)是指具有自動存儲期限和臨時生存期的對象。 它的生命周期從對表達式進行求值開始,並且其初始值為表達式的值。 當包含完整表達式或完整聲明符的求值結束時,其生存期結束。 任何試圖使用臨時生存期修改對象的嘗試都會導致未定義的行為。

因此,程序的行為在C11中得到了很好的定義。 但是,在能夠獲得符合C11的編譯器之前,最好的選擇可能是將函數的結果存儲在本地對象中(假設您的目標是工作代碼而不是破壞編譯器):

[...]
int main(void ) {
    struct Foo temp = foo();
    printf("%s\n", temp.f);
}

printf有點有趣,因為它是使用varargs的那些函數之一。 因此,讓我們通過編寫輔助功能bar將其分解。 稍后我們將返回至printf

(我正在使用“ gcc(Ubuntu 4.4.3-4ubuntu5)4.4.3”)

void bar(const char *t) {
    printf("bar: %s\n", t);
}

然后調用它:

bar(foo().f); // error: invalid use of non-lvalue array

好的,這會導致錯誤。 在C和C ++中,不允許通過value傳遞數組。 您可以通過將數組放入結構中來解決此限制,例如void bar2(Foo f) {...}

但是我們沒有使用該解決方法-我們不允許按值傳遞數組。 現在,您可能認為它應該衰減為char* ,從而允許您通過引用傳遞數組。 但是衰減僅在數組具有地址(即左值)的情況下有效。 但是臨時變量(例如,函數的返回值)生活在沒有地址的神奇土地上。 因此,您不能使用臨時地址&地址。 簡而言之,我們不允許使用臨時地址,因此它不能衰減到指針。 我們無法通過值(因為它是一個數組)或引用(因為它是臨時的)來傳遞它。

我發現以下代碼有效:

bar(&(foo().f[0]));

但說實話,我認為那是可疑的。 這是否違反了我剛剛列出的規則?

只是為了完整起見,這完全可以正常工作:

Foo f = foo();
bar(f.f);

變量f不是臨時變量,因此我們可以(隱式地在衰減期間)獲取其地址。

printf,32位和64位以及怪異現象

我答應再次提及printf 根據以上所述,它應該拒絕將foo()。f傳遞給任何函數(包括printf)。 但是printf很有趣,因為它是這些vararg函數之一。 gcc允許自己通過值將數組傳遞給printf。

當我第一次編譯並運行代碼時,它處於64位模式。 在以32位(從-m32到gcc)進行編譯之前,我看不到理論的證實。 像最初的問題一樣,我確實遇到了段錯誤。 (使用64位時,我一直得到一些混亂的輸出,但沒有段錯誤)。

我實現了自己的my_printf (使用vararg廢話),在嘗試打印char*指向的字母之前,它打印了char *的實際值。 我這樣稱呼它:

my_printf("%s\n", f.f);
my_printf("%s\n", foo().f);

這是我得到的輸出( ideone上的代碼 ):

arg = 0xffc14eb3        // my_printf("%s\n", f.f); // worked fine
string = Hello, World!
arg = 0x6c6c6548        // my_printf("%s\n", foo().f); // it's about to crash!
Segmentation fault

第一個指針值0xffc14eb3是正確的(它指向字符“ Hello,world!”),但是請看第二個指針值0x6c6c6548 那是Hell的ASCII碼(反序-小端序或類似的東西)。 它已按值將數組復制到printf中,並且前四個字節已解釋為32位指針或整數。 該指針沒有指向任何明智的位置,因此,當它嘗試訪問該位置時,程序將崩潰。

我認為這是違反標准的,僅僅是因為我們不允許我們按值復制數組。

在MacOS X 10.7.2上,兩個GCC / LLVM 4.2.1('i686-apple-darwin11-llvm-gcc-4.2(GCC)4.2.1(基於Apple Inc.內部版本5658)(LLVM內部版本2335.15.00)' )和GCC 4.6.1(我構建的)在32位和64位模式下均在沒有警告的情況下(在-Wall -Wextra下)編譯代碼。 程序全部運行而不會崩潰。 這就是我所期望的; 代碼對我來說看起來不錯。

也許Ubuntu上的問題是特定版本的GCC中的錯誤,此錯誤已得到修復?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM