c++ 如何用一个已有的静态数组初始化一个c-style结构中的静态数组?

anauzrmj  于 2022-11-19  发布在  其他
关注(0)|答案(4)|浏览(138)

我目前正在用OpenCL做c++,其中需要一个c风格的结构体来将配置信息从c++主机传送到OpenCL内核。鉴于动态分配的数组并不保证每个OpenCL实现都支持,我必须确保内核代码可访问的每个数组都是静态大小的。然而,在初始化c风格结构体中的静态数组时,我遇到了奇怪的错误。
以下PoC可能会重现该错误:

#include <cstring>
#include <string>
#define ID_SIZE 16

struct conf_t {
    const unsigned int a;
    const unsigned int b;
    const unsigned char id[ID_SIZE];
};

int main() {
    const std::string raw_id("0123456789ABCDEF");
    unsigned char id[ID_SIZE];
    memcpy(id,raw_id.c_str(),ID_SIZE);
    struct conf_t conf = {10,2048,id};
}

并出现以下错误:

poc.cc: In function ‘int main()’:
poc.cc:15:39: error: array must be initialized with a brace-enclosed initializer
   15 |         struct conf_t conf = {10,2048,id};
      |                                       ^~

我确实可以删除结构体中的const关键字,并去掉堆栈变量id,其中&(conf.id)可以是memcpy的第一个参数。但是,我希望保持conf结构体中字段的不可变性,这使编译器能够检查不需要的修改。
就我的理解而言,c中的struct应该具有以下内存布局:

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               a                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               b                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
+                                                               +
|                                                               |
+                               id                              +
|                                                               |
+                                                               +
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

既然堆栈变量id也是静态大小的,我很困惑为什么即使id已经是一个静态大小的数组,c++编译器仍然要寻找一个用大括号括起来的初始化器。

ujv3wf0j

ujv3wf0j1#

如果你想复制整个字符串,你必须使用memcopyconf.id(或者strncpy,如果它保证是一个以零结尾的字符串)。不幸的是,这意味着conf_t中的id不再是const

#include <iostream>
#include <cstring>
#include <string>
#define ID_SIZE 16

struct conf_t
{
    const unsigned int a;
    const unsigned int b;
    unsigned char id[ID_SIZE];
};

int main()
{
    const std::string raw_id("0123456789ABCDE");
    conf_t conf = {10, 2048, {0}};
    memcpy(conf.id, raw_id.c_str(), ID_SIZE); // <- memcopy from the string into conf.id

    std::cout << conf.id << '\n';

    std::cout << std::boolalpha;
    std::cout << std::is_pod<conf_t>::value << '\n';
}

另一方面,如果conf_t.id必须是const,那么我相信你必须使用一个编译时常量初始化来保持conf_t是一个POD类:

struct conf_t
{
    const unsigned int a;
    const unsigned int b;
    const unsigned char id[ID_SIZE];
};

int main()
{
    conf_t conf = {10, 2048, "0123456789ABCDE"};
...

也可以使用模板构造函数将动态数组转换为初始化列表。这将使您能够用动态数据初始化const c数组,但它会向conf_t添加一个构造函数,这意味着它不再是POD类。

#include <iostream>
#include <cstring>
#include <string>
#include <utility>
#define ID_SIZE 16

struct conf_t
{
    const unsigned int a;
    const unsigned int b;
    const unsigned char id[ID_SIZE];

    conf_t(const unsigned int a,
           const unsigned int b,
           const unsigned char (&arr)[ID_SIZE])
        : conf_t(a, b, arr, std::make_index_sequence<ID_SIZE>())
    {
    }

private:
    template <std::size_t... Is>
    conf_t(const unsigned int a,
           const unsigned int b,
           const unsigned char (&arr)[ID_SIZE], std::index_sequence<Is...>)
        : a{a},
          b{b},
          id{arr[Is]...}
    {
    }
};

int main()
{
    const std::string raw_id("0123456789ABCDE");
    unsigned char id[ID_SIZE];
    memcpy(id, raw_id.c_str(), ID_SIZE);
    conf_t conf = {10, 2048, id};

    std::cout << conf.a << '\n';
    std::cout << conf.b << '\n';
    std::cout << conf.id << '\n';

    std::cout << std::boolalpha;
    std::cout << std::is_pod<conf_t>::value << '\n';
}

这是可能的,我已经错过了一些虽然,所以我欢迎任何更正。

6qftjkof

6qftjkof2#

请尝试以下语法之一:

struct conf_t {
    const unsigned int a;
    const unsigned int b;
    const unsigned char id[ID_SIZE];
};

conf_t syntax_1 = { 10, 1, { 'a', 'b', 'c' }};  // an array needs its own {}
conf_t syntax_2 = { 10, 1, "hello" };           // an array of char can be a string.
                                                // make sure you have room for the
                                                // null termination!
0yg35tkg

0yg35tkg3#

你的声明有些奇怪。我怀疑你的大多数问题都来自const的滥用。
conf_t定义如下:

struct conf_t { 
    const unsigned int a;
    const unsigned int b;
    const unsigned char id[ID_SIZE];
};

为什么要声明常量成员?为什么它们没有初始化?它们都声明为const,却没有一个被初始化,这让我怀疑这是一个很大的代码味道。如果你需要一个常量conf_t,那么就用通常的方式声明它,如下所示:

struct conf_t { 
    unsigned int a;            // note that the const keyword has disappeared.
    unsigned int b;
    unsigned char id[ID_SIZE];
};

// the conf_t variable is declared const..

const conf_t my_conf { 10, 2048, "0123456789ABCDE" };

// et voilà !

如果您需要更大的灵活性,(即:从左值初始化id),您可以按照@Frodyne的建议定义一个构造函数,或者创建一个初始化函数,这种解决方案的优点是保留了简单conf_t结构的POD属性。
示例:

struct conf_t { 
    unsigned int a;
    unsigned int b;
    unsigned char id[ID_SIZE];  // is there a VERY good reason why this is unsigned char?
};

// initializes a conf_t, I guess you cpould call that a 
// pseudo-constructor of sorts.

conf_t make_conf(int a, int b, const char* id)
{
     // our contract:
     assert(id != NULL && strlen(id) < ID_SIZE);  // or strlen(id) == ID_SIZE - 1, if you wish

     conf_t result = { a, b };
     strncpy((char*)result.id, id, sizeof(result.id));
     result.id[sizeof(result.id) - 1] = 0;

     // all c++ compilers less than 20 years old will return in-place
     return result;
}

//  declaration from literals then becomes:

const conf_t cfg1 = make_conf(1, 2, "0123456789ABCDE");

//  declaration from l-values becomes:

int x = 123; 
int y = 42;
std::string s = "EDCBA9876543210";

const conf_t cfg2 = make_conf(x, y, s.c_str());

这不会对手指和眼睛造成太大的负担。

iyfamqjs

iyfamqjs4#

初始化时的错误是无法使用unsigned char[16]类型的左值初始化const unsigned char类型的数组元素。
你需要初始化你的成员,使用正确的初始化语法和花括号{ vals... },并使用正确的初始化值类型。但是,这在标准C++中并不容易,因为你试图从non-const初始化const值。
一个不丢失id成员中的const的解决方法是简单地将其更改为const unsigned char*
然后,您可以使用C++中存在的一些模糊怪物,如reinterpret_cast<T>,并从一个非常数成员初始化您的常量成员。

#include <cstring>
#include <string>
#define ID_SIZE 16

struct conf_t {
    const unsigned int a;
    const unsigned int b;
    const unsigned char* id[ID_SIZE];
};

int main() {
    const std::string raw_id("0123456789ABCDEF");
    unsigned char id[ID_SIZE];
    memcpy(id, raw_id.c_str(), ID_SIZE);
    struct conf_t conf = {10, 2048, reinterpret_cast<const unsigned char*>(id)};
    std::cout << *(conf.id);
}

现在一切都编译好了。您可以看到live example here

相关问题