簡體   English   中英

對結構成員的不良訪問

[英]Inelegant Access to Struct Members

由於structs及其成員的內存布局方式,我能夠做到以下幾點:

typedef struct
{
    int a;
    int b;
    int c;
} myStruct;

main()

myStruct aStruct;
aStruct.a = 1;
aStruct.b = 2;
aStruct.c = 3;

int *aBadBoyPointer = &aStruct.a;

printf("%i %i %i", *(aBadBoyPointer), *(++aBadBoyPointer), *(++aBadBoyPointer));

很容易。

以上行引發警告:

無序修改和訪問aBadBoyPointer

但它分別編譯和運行精細打印出1, 2, 3

我的問題在這里:

為了正確地做到這一點,你能舉出一個可能會破壞的場景嗎,這個場景證實了編譯器這是一個糟糕的做法/糟糕的做事方式?

或者:或許這實際上是在一些罕見情況下做事的“好方法”?

附錄:

除了導致未定義行為的這部分:

printf("%i %i %i", *(aBadBoyPointer), *(++aBadBoyPointer), *(++aBadBoyPointer));

我真正想知道的是:

它是考慮OK(好的做法)使用指針指向的成員之一struct ,但隨后訪問其他成員內部struct這種方式(通過遞增,由於內存的布局成為可能struct成員)或這是一種標准不贊成的做法嗎?

其次如上所述,如果這是一個不好的做法,那么是否會出現在結構中以這種方式使用指針然后獲得對某些情況下有益的另一個成員變量的訪問的情況?

有一些問題,您看到的警告是由於您的函數參數的評估順序未指定 C99標准草案6.5.2.2函數調用10段說:

函數指示符的評估順序,實際參數和實際參數中的子表達式是未指定的,但在實際調用之前有一個序列點。

你也在序列點內多次修改變量,這是一個未定義的行為 ,不能依賴於它的工作,第6.5表達式2段說( 強調我的前進 ):

在前一個和下一個序列點之間,對象的存儲值最多只能通過表達式的計算修改一次。 72)此外,先前值應只讀以確定要存儲的值。 73)

另外,請注意,標准允許在結構元素之間填充,但超出該標量被認為是一個元素的數組,因此增加超出數組然后執行間接也將是未定義的。 6.5.6 添加劑操作員7節中對此進行了介紹:

出於這些運算符的目的, 指向不是數組元素的對象的指針與指向長度為1 的數組的第一個元素的指針的行為相同,其中對象的類型為其元素類型。

6.5.6 附加運算符8段中未定義6.5.6接近數組邊界的數組邊界的數據邊界。

[...]如果指針操作數和結果都指向同一個數組對象的元素,或者指向數組對象的最后一個元素,則評估不應產生溢出; 否則,行為未定義。 如果結果指向數組對象的最后一個元素之后,則不應將其用作已計算的一元*運算符的操作數。

我們可以看到gcc將根據優化級別輸出( 實時查看 ):

3 3 2

或( 現場直播 ):

3 3 3

這兩者都不是理想的輸出。

通過指針訪問結構成員的標准兼容方法是使用offsetofhere ,這需要包括stddef.h 訪問成員a看起來像這樣:

*( (int*) ((char*)aBadBoyPointer+offsetof(myStruct, a)) )
    ^       ^                    ^
    3       2                    1

這里有三個要素:

  1. 使用offsetof確定成員的偏移量(以字節單位)
  2. 轉換為* char **,因為我們需要以字節為單位的指針算法
  3. 回到* int **,因為這是正確的類型

我同意現有的答案(這種未經測試的訪問會調用Undefined Behavior並且很糟糕)。

但是,為了舉一個具體的例子,我在MS Visual Studio中編譯了你的代碼。 輸出是(在調試和釋放模式下):

3
3
3

函數參數不按C標准確定的順序進行評估(C99§6.5.2.2),因此您的代碼會調用未定義的行為。 不同平台上的不同編譯器或相同編譯器可能會給您不同的結果。 在任何情況下,調用未定義的行為都是一種很好的方法。

作為參考,標准的文字說:

10函數指示符的評估順序,實際參數和實際參數中的子表達式是未指定的,但在實際調用之前有一個序列點。

附錄

要回答問題的第二部分,C編譯器可以根據§6.7.2.1第12段在結構成員之間添加填充:

結構或聯合對象的每個非位字段成員以適合其類型的實現定義方式對齊。

在某些情況下,結構可以像數組一樣運行,並且遞增指向成員的指針可以為您解決(請參閱#pragma pack__attribute__((packed)) )但是您的代碼將明確地(如果明確地)不可移植並且您可能遇到一些編譯器錯誤。 通常,使用數組和枚舉來代替結構。

除了在Shafik Yaghmournmichaels答案中已經說過的內容之外 ,您還必須注意到一些編譯器將對結構中的變量應用對齊 ,通常為4個字節。 例如:

struct something {
    char a;
    char b;
};

這個結構似乎有兩個字節,但它可能有8個,因為編譯器可能會填充結構中的每個元素,使其覆蓋一個可被4整除的內存空間。將有6個字節只是垃圾,但它們仍然是保留的。 在這樣的例子中,將結構作為一系列char讀取將失敗。

編譯器可以在每個struture的成員之間添加填充。 如果確實如此,那么OP的代碼就會失敗。


此外,這可能是未定義的行為,因為人們可能無法取消引用指向數組邊界的指針。

但是,是否可以考慮

int a;

一個1元素的數組我不確定。

暫無
暫無

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

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