簡體   English   中英

靈活數組成員在 C++ 中有效嗎?

[英]Are flexible array members valid in C++?

在 C99 中,您可以像這樣聲明結構的靈活數組成員:

struct blah
{
    int foo[];
};

但是,當工作中的某個人嘗試在 C++ 中使用 clang 編譯一些代碼時,該語法不起作用。 (它一直在與 MSVC 一起工作。)我們必須將其轉換為:

struct blah
{
    int foo[0];
};

查看 C++ 標准,我發現根本沒有提到靈活的成員數組; 我一直認為[0]是無效聲明,但顯然對於靈活的成員數組它是有效的。 靈活的成員數組在 C++ 中實際上有效嗎? 如果是這樣,正確的聲明是[]還是[0]

C++ 於 1998 年首次標准化,因此它早於向 C(C99 中的新成員)添加靈活數組成員。 2003 年對 C++ 進行了更正,但沒有添加任何相關的新功能。 C++ (C++2b) 的下一個修訂版仍在開發中,似乎還沒有添加靈活的數組成員。

C++ 不支持結構末尾的 C99 靈活數組成員,無論是使用空索引符號還是0索引符號(除非供應商特定的擴展):

struct blah
{
    int count;
    int foo[];  // not valid C++
};

struct blah
{
    int count;
    int foo[0]; // also not valid C++
};

據我所知,C++0x 也不會添加這個。

但是,如果將數組大小調整為 1 個元素:

struct blah
{
    int count;
    int foo[1];
};

代碼將編譯,並且運行良好,但它在技術上是未定義的行為。 您可以使用不太可能出現非一錯誤的表達式分配適當的內存:

struct blah* p = (struct blah*) malloc( offsetof(struct blah, foo[desired_number_of_elements]);
if (p) {
    p->count = desired_number_of_elements;

    // initialize your p->foo[] array however appropriate - it has `count`
    // elements (indexable from 0 to count-1)
}

因此它可以在 C90、C99 和 C++ 之間移植,並且與 C99 的靈活數組成員一樣有效。

Raymond Chen 對此寫了一篇很好的文章:為什么有些結構以大小為 1 的數組結尾?

注意:在 Raymond Chen 的文章中,初始化“flexible”數組的示例中存在拼寫錯誤/錯誤。 它應該是:

for (DWORD Index = 0; Index < NumberOfGroups; Index++) { // note: used '<' , not '='
  TokenGroups->Groups[Index] = ...;
}

第二個將不包含元素,而是在blah之后立即指向。 所以如果你有這樣的結構:

struct something
{
  int a, b;
  int c[0];
};

你可以做這樣的事情:

struct something *val = (struct something *)malloc(sizeof(struct something) + 5 * sizeof(int));
val->a = 1;
val->b = 2;
val->c[0] = 3;

在這種情況下, c將表現為具有 5 個int的數組,但數組中的數據將在something結構之后。

我正在開發的產品使用它作為一個大小的字符串:

struct String
{
  unsigned int allocated;
  unsigned int size;
  char data[0];
};

由於支持的體系結構,這將消耗 8 個字節加上allocated .

當然,所有這些都是 C 語言,但例如 g++ 可以毫無障礙地接受它。

如果您可以將您的應用程序限制為僅需要幾個已知大小,那么您可以使用模板有效地實現靈活的數組。

template <typename BASE, typename T, unsigned SZ>
struct Flex : public BASE {
    T flex_[SZ];
};

如果你只想

struct blah { int foo[]; };

那么你根本不需要結構體,你可以簡單地處理一個 malloc'ed/new'ed int 數組。

如果一開始有一些成員:

struct blah { char a,b; /*int foo[]; //not valid in C++*/ };

然后在 C++ 中,我想你可以用foo成員函數替換foo

struct blah { alignas(int) char a,b; 
    int *foo(void) { return reinterpret_cast<int*>(&this[1]); } };

使用示例:

#include <stdlib.h>
struct blah { 
    alignas(int) char a,b; 
    int *foo(void) { return reinterpret_cast<int*>(&this[1]); }
};
int main()
{
    blah *b = (blah*)malloc(sizeof(blah)+10*sizeof(int));
    if(!b) return 1;
    b->foo()[1]=1;
}

一項提案正在進行中,可能會成為未來的 C++ 版本。 有關詳細信息,請參閱http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1039r0.html (該提案相當新,因此可能會發生變化)

我面臨同樣的問題來聲明一個可以從 C++ 代碼中使用的靈活數組成員。 通過查看glibc頭文件,我發現有一些靈活數組成員的用法,例如在struct inotify中聲明如下(省略了注釋和一些不相關的成員):

struct inotify_event
{
  //Some members
  char name __flexarr;
};

__flexarr宏又被定義為

/* Support for flexible arrays.
   Headers that should use flexible arrays only if they're "real"
   (e.g. only if they won't affect sizeof()) should test
   #if __glibc_c99_flexarr_available.  */
#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
# define __flexarr  []
# define __glibc_c99_flexarr_available 1
#elif __GNUC_PREREQ (2,97)
/* GCC 2.97 supports C99 flexible array members as an extension,
   even when in C89 mode or compiling C++ (any version).  */
# define __flexarr  []
# define __glibc_c99_flexarr_available 1
#elif defined __GNUC__
/* Pre-2.97 GCC did not support C99 flexible arrays but did have
   an equivalent extension with slightly different notation.  */
# define __flexarr  [0]
# define __glibc_c99_flexarr_available 1
#else
/* Some other non-C99 compiler.  Approximate with [1].  */
# define __flexarr  [1]
# define __glibc_c99_flexarr_available 0
#endif

我不熟悉MSVC編譯器,但可能您必須根據MSVC版本再添加一個條件宏。

標准 C++ 不支持靈活的數組成員,但是 clang 文檔說。

“除了此處列出的語言擴展之外,Clang 還旨在支持廣泛的 GCC 擴展。”

C++ 的 gcc 文檔說。

“GNU 編譯器為 C++ 語言提供了這些擴展(您也可以在 C++ 程序中使用大多數 C 語言擴展)。”

C 文檔的 gcc 文檔支持零長度數組。

https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

靈活數組還不是 C++ 標准的一部分。 這就是int foo[]int foo[0]可能無法編譯的原因。 雖然有一個提案正在討論中,但它尚未被 C++ (C++2b) 的最新修訂版接受。

但是,幾乎所有現代編譯器都通過編譯器擴展支持它。

問題是,如果您以最高警告級別 ( -Wall --pedantic ) 使用此擴展程序,則可能會導致警告。

一種解決方法是使用一個包含一個元素的數組並進行越界訪問。 雖然這個解決方案是規范( dcl.arrayexpr.add )的UB,但大多數編譯器都會產生有效的代碼,甚至clang -fsanitize=undefined對它很滿意:

#include <new>
#include <type_traits>

struct A {
    int a[1];
};

int main()
{
    using storage_type = std::aligned_storage_t<1024, alignof(A)>;
    static storage_type memory;
    
    A *ptr_a = new (&memory) A;

    ptr_a->a[2] = 42;
    
    return ptr_a->a[2];
}

演示


盡管如此,如果您希望代碼符合標准並且不依賴於任何編譯器擴展,則必須避免使用此功能。

更好的解決方案是將其聲明為指針:

struct blah
{
    int* foo;
};

或者更好的是,將其聲明為std::vector

struct blah
{
    std::vector<int> foo;
};

暫無
暫無

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

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