簡體   English   中英

_Static_assert 在未使用的通用選擇中

[英]_Static_assert in unused generic selection

看起來typeof運算符可能會被下一個 C 標准接受,我正在尋找是否有辦法利用它來使用可移植的 ISO-C 創建一個宏,該宏可以獲取傳入的數組的長度如果將指針傳遞給它,則編譯失敗。 通常,泛型選擇可用於在使用不需要的類型時通過將其排除在泛型關聯列表之外來強制編譯器錯誤,但在這種情況下,我們需要一個默認關聯來處理任何長度的 arrays,所以我正在嘗試為我們不想要的類型的泛型關聯強制編譯器錯誤。 下面是宏的示例:

#define ARRAY_SIZE(X) _Generic(&(X), \
        typeof(&X[0]) *: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
        default: (sizeof(X) / sizeof(X[0])) \
)

問題是即使選擇的通用關聯是默認關聯, _Static_assert也會跳閘。 為了簡單起見,由於手頭的問題與 C23 中引入的任何內容無關,我們將制作一個測試程序,該程序可以明確地拒絕指向 int 的指針:

#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE(X) _Generic(&(X), \
        int **: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
        default: (sizeof(X) / sizeof(X[0])) \
)

int main(void) {

    int x[100] = {0};
    int *y = x;
    int (*z)[100] = {&x};

    printf("length of x: %zu\n", ARRAY_SIZE(x));
    printf("length of y: %zu\n", ARRAY_SIZE(y));
    printf("length of z: %zu\n", ARRAY_SIZE(z));
    printf("length of *z: %zu\n", ARRAY_SIZE(*z));
    return EXIT_SUCCESS;

}

使用-std=c11構建上面的內容,我發現_Static_assertARRAY_SIZE的所有擴展上跳閘,而我預計只有使用int **泛型關聯的指針有問題。

根據通用選擇的 C11 標准的 6.5.1.1 p3,

來自通用選擇的任何其他通用關聯的表達式均未被評估

這是 gcc 和 clang 中的一個錯誤,還是我在標准中遺漏了什么會導致在未使用的通用關聯中對此_Static_assert進行編譯時評估?

評估哪個通用選擇並不重要。

當作為_Status_assert一部分的表達式的值為 0 時,這被視為約束違規,編譯器需要生成診斷。

您不能真正將_Static_assert與應返回值的表達式混合使用,例如類似函數的宏。 您也許可以使用“窮人的 static 斷言”來解決這個問題,就像我們在 C11 之前使用的丑陋技巧之一:

#define POOR_STATIC_ASSERT(expr) (int[expr]){0}

#define CHECK(X) _Generic((&X), \
        int **: 0,\
        default: (sizeof(X) / sizeof(X[0])) \
)

#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )

這里調用逗號運算符讓宏CHECK返回大小或零,以防類型有效或無效。 然后再次調用同一個宏,從類似函數的宏ARRAY_SIZE返回那個宏。 這將導致來自 ISO C 編譯器的一些神秘錯誤,例如“錯誤:ISO C 禁止零大小數組”。


下一個問題是_Generic中的&(X)決不能保證歸結為int** ,因此這個宏不安全或不可靠。 不過,關於數組大小,我們可以使用一個技巧。 指向無大小(不完整類型)數組的指針與相同元素類型的每個數組兼容,無論其大小如何。 宏可以重寫為:

#define POOR_STATIC_ASSERT(expr) (int[expr]){0}

#define CHECK(X) _Generic((&X),              \
        int (*)[]: sizeof(X) / sizeof(X[0]), \
        default: 0)

#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )

這將適用於任何int數組,無論大小如何,但對其他一切都失敗。

利用倫丁回答中的一些建議,我想出了以下解決簡化問題的方法:

#define STATIC_ASSERT_EXPRESSION(X, ERROR_MESSAGE) (sizeof(struct {_Static_assert((X), ERROR_MESSAGE); int _a;}))

#define NO_POINTERS(X) _Generic(&(X), \
    int (*)[]: 1, \
    default: 0 \
)

#define ARRAY_SIZE(X) ( (void)STATIC_ASSERT_EXPRESSION(NO_POINTERS(X), "Cannot retrieve the number of array elements from a pointer"), (sizeof(X) / sizeof(X[0])) )

對於使用 typeof 的類型泛型的實際用例,它應該進入 C23 標准,使用以下代碼替換NO_POINTERS宏:

#define NO_POINTERS(X) _Generic(&(X), \
    typeof(*X) (*)[]: 1, \
    default: 0 \
)

通過將_Static_assert通用選擇之外,它只會使用從選擇中實際返回的值進行評估,因此它不會因為存在於未使用的選擇中而被觸發。 此外,還從通用選擇中刪除了元素數量計算,以便可以在 Static 斷言中安全地使用通用選擇的表達式,即使您的數組是可變長度數組,需要在運行時計算其大小.

Static 斷言本身放置在一個匿名結構中,我們采用 sizeof 使其成為表達式的一部分。 然后就像在 Lundin 的示例中一樣,我們使用逗號運算符計算該表達式,然后拋出並使用數組大小計算的結果。

有了這個,我們拒絕指針,同時獲取 static arrays 和 VLA 中的元素數量,此外,我們在嘗試傳入指針時得到一條很好的編譯器錯誤消息。

暫無
暫無

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

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