C ++ 11:将结构数组重新解释为struct的成员数组

时间:2016-07-29 23:55:24

标签: c++ c++11 reinterpret-cast

考虑以下类型:

struct S
{
    char v;
};

给定一个const S数组,是否可以以符合标准的方式,将其重新解释为const char的数组,其元素对应于每个原始数组元素的成员v,反之亦然?例如:

const S a1[] = { {'a'}, {'4'}, {'2'}, {'\0'} };
const char* a2 = reinterpret_cast< const char* >(a1);

for (int i = 0; i < 4; ++i)
    std::cout << std::boolalpha << (a1[i].v == a2[i]) << ' ';

上面的代码是否可移植,是否会打印true true true true?如果没有,还有其他方法可以达到这个目的吗?

显然,可以创建一个新数组并使用原始数组的每个元素的成员v对其进行初始化,但整个想法是避免创建新数组。

4 个答案:

答案 0 :(得分:8)

平凡,没有 - struct可能有填充。而这种平​​局打破了任何对阵列的重新解释。

答案 1 :(得分:6)

正式struct可能有填充,因此其大小大于1.

即,正式地你不能reinterpret_cast并拥有完全可移植的代码,除了只有一个项目的¹an数组。

但是对于实践中,几年前有人问过,现在是否有任何编译器默认会为sizeof(T) > 1提供struct T{ char x; };。我还没有看到任何例子。因此,在实践中,只需要static_assert大小为1,并且根本不担心这个static_assert在某些系统上会失败。

即,

S const a1[] = { {'a'}, {'4'}, {'2'}, {'\0'} };
static_assert( sizeof( S ) == 1, "!" );

char const* const a2 = reinterpret_cast<char const*>( a1 );

for( int i = 0; i < 4; ++i )
{
    assert( a1[i].v == a2[i] );
}

由于可能以索引具有未定义行为的方式解释C ++ 14及更高版本的标准,基于对“数组”的特殊解释,指的是某些原始数组,可能会用更笨拙和冗长但有保证的有效方式编写此代码:

// I do not recommend this, but it's one way to avoid problems with some compiler that's
// based on an unreasonable, impractical interpretation of the C++14 standard.
#include <assert.h>
#include <new>

auto main() -> int
{
    struct S
    {
        char v;
    };

    int const compiler_specific_overhead    = 0;    // Redefine per compiler.
    // With value 0 for the overhead the internal workings here, what happens
    // in the machine code, is the same as /without/ this verbose work-around
    // for one impractical interpretation of the standard.
    int const n = 4;
    static_assert( sizeof( S ) == 1, "!" );
    char storage[n + compiler_specific_overhead]; 
    S* const a1 = ::new( storage ) S[n];
    assert( (void*)a1 == storage + compiler_specific_overhead );

    for( int i = 0; i < n; ++i ) { a1[i].v = "a42"[i]; }    //  Whatever

    // Here a2 points to items of the original `char` array, hence no indexing
    // UB even with impractical interpretation of the C++14 standard.
    // Note that the indexing-UB-free code from this point, is exactly the same
    // source code as the first code example that some claim has indexing UB.
    char const* const a2 = reinterpret_cast<char const*>( a1 );

    for( int i = 0; i < n; ++i )
    {
        assert( a1[i].v == a2[i] );
    }
}

注意:
¹标准保证struct开头没有填充。 功能

答案 2 :(得分:4)

a2[i]中的指针算术未定义,参见C ++ 14 5.7 [expr.add] p7:

  

对于加法或减法,如果表达式PQ具有类型“指向 cv T的指针”,其中T和数组元素类型不相似(4.5),行为未定义。 [注意:特别是,当数组包含派生类类型的对象时,指向基类的指针不能用于指针算术。 - 结束说明]

由于此规则,即使没有填充且大小匹配,基于类型的别名分析也允许编译器假定a1[i]a2[i]不重叠(因为指针算术是仅当a2确实是char的数组时才有效,而不仅仅是具有相同大小和对齐的数组,并且如果它实际上是char的数组,则它必须是与数组中的单独对象S)。

答案 3 :(得分:2)

如果源数据是常数,我认为我倾向于使用编译时转换:

#include <iostream>
#include <array>

struct S
{
    char v;
};

namespace detail {
    template<std::size_t...Is>
    constexpr auto to_cstring(const S* p, std::index_sequence<Is...>)
    {
        return std::array<char, sizeof...(Is)> {
            p[Is].v...
        };
    }
}

template<std::size_t N>
constexpr auto to_cstring(const S (&arr)[N])
{
    return detail::to_cstring(arr, std::make_index_sequence<N>());
}

int main()
{
    const /*expr if you wish*/ S a1[] = { {'a'}, {'4'}, {'2'}, {'\0'} };

    const /*expr if you wish*/ auto a2 = to_cstring(a1);


    for (int i = 0; i < 4; ++i)
        std::cout << std::boolalpha << (a1[i].v == a2[i]) << ' ';
}

输出:

true true true true

即使数据不是constexpr,gcc和clang也非常擅长这样的常量折叠复杂序列。