类的成员字段顺序是“稳定的”吗?

时间:2014-05-05 19:16:38

标签: c++ casting memory-alignment

考虑c ++(或c ++ 11),其中我有一些数据,其中2 * N整数代表N对。对于每个偶数i = 0,2,4,6,...,2 * N,它认为(data [i],data [i + 1])形成这样的一对。现在我希望有一种简单的方法来访问这些对,而无需编写如下的循环:

for(int i=0; i<2*N; i+=2) { ... data[i] ... data[i+1] ... }

所以我写了这个:

#include <iostream>

struct Pair {
    int first; int second;
};

int main() {
    int N=5;
    int data[10]= {1,2,4,5,7,8,10,11,13,14};
    Pair *pairs = (Pair *)data;

    for(int i=0; i<N; ++i)
        std::cout << i << ": (" << pairs[i].first << ", " << pairs[i].second << ")" << std::endl;

    return 0;
}

输出:

0: (1, 2)
1: (4, 5)
2: (7, 8)
3: (10, 11)
4: (13, 14)

ideaone:http://ideone.com/DyWUA8

正如您所看到的,我将int指针强制转换为Pair指针,这样c ++只会处理我的数据大小为int的两倍。我知道,因为这就是数组的工作方式,数据数组是以两个sizeof(int)对的方式对齐的。但是,我不确定我是否可以假设一个Pair正好是两个sizeof(int)的,并且第一个和第二个成员字段是以该顺序(或对齐)存储的。从技术上讲,在最糟糕的情况下,我可以想象编译器首先存储2个字节,然后存储4个第二个字节,然后存储第一个字节(假设int是4个字节),并以某种方式管理它。当然,这可能是荒谬的,但它是否允许在c ++中?

请注意,我不想将所有数据复制到新阵列并手动将其转换为Pairs。 Imho,这对于语法糖来说是一项昂贵的操作。

我可以假设Pair类对齐吗?结构是否相同?还有其他方法吗?

从我在这里阅读(How is the size of a C++ class determined?),c ++的编译器,而不是语言,是如何在内存中对齐类的。 这是否意味着我注定要复制我的数据使用讨厌的语法?我可以以某种方式强制c ++语言中的最小对齐,还是需要编译器切换?

3 个答案:

答案 0 :(得分:2)

您所做的事情违反了严格的别名规则,因此除了任何可能的大小和对齐问题外,还会导致未定义的行为。

最干净的解决方案是通过在需要时进行一次性转换,以逻辑对而不是平面数据存储数据。不要担心执行数据转换的性能,除非分析显示您正在执行时间。将数据分组成对的清晰度几乎肯定会在长期内得到正确回报。

或者,你可以创建内联函数来抽象地访问名义上的&#34;第一个&#34;和&#34;第二&#34;平面数组数据的属性。

答案 1 :(得分:2)

结构的对齐至少是其成员的最大对齐方式,但它可以更大。此外,编译器可以在您的成员之间添加填充,因此您的代码不安全。

基本上,结构布局的唯一保证是:

  1. 第一个成员位于偏移0处。
  2. 内存中的成员顺序与代码中的相同。
  3. 您可以使用此定义的第一个保证:

    struct Pair {
        int p[2];
    };
    

    现在,sizeof(Pair)可能比2*sizeof(int)更大,但这不应该太重要。

    或者,如果你想要额外的乐趣:

    typedef int Pair[2];
    

    指向数组的指针很有趣!

    无论如何,我的建议是这样做:

    int data[10]= {1,2,4,5,7,8,10,11,13,14};
    
    for(int i=0; i<N; ++i)
    {
        int *pair = data + 2*i;
        std::cout << i << ": (" << pairs[0] << ", " << pairs[1] << ")" << std::endl;
    }
    

    或者如果您更喜欢额外的乐趣:

    typedef int Pair[2];
    int data[10]= {1,2,4,5,7,8,10,11,13,14};
    Pair *pairs = (Pair*)data;
    
    for(int i=0; i<N; ++i)
    {
        std::cout << i << ": (" << pairs[i][0] << ", " << pairs[i][1] << ")" << std::endl;
    }
    

答案 2 :(得分:1)

  

这是否意味着我注定要复制我的数据或使用讨厌的语法?

没有

  

还有其他方法吗?

是的,使用提供您喜欢的语法的包装类。这是一种方式

http://ideone.com/nitI0B

#include <iostream>

struct Pairs {
    int* _data;
    Pairs( int data[] ) : _data(data) {}
    int & first( size_t x ) const { return _data[x*2]; }
    int & second( size_t x ) const { return _data[x*2+1]; }
};

int main() {
    int N=5;
    int data[10]= {1,2,4,5,7,8,10,11,13,14};
    Pairs pairs( data );

    for(int i=0; i<N; ++i)
        std::cout << i << ": (" << pairs.first(i) << ", " << pairs.second(i) << ")" << std::endl;

    return 0;
}

<强>更新

这是一个在结构(如C ++ 11 std :: array)中包装int [2]的解决方案,但在int [2]之后允许编译器填充(实际上是强制)填充。编译器不太可能添加任何额外的填充,但标准并不排除它。我还添加了一个随机访问迭代器,允许将迭代器传递给std :: sort并将原始数据按对进行排序。我为我的一次教育做了这个,可能比它的价值更麻烦。

See this in ideone

// http://stackoverflow.com/questions/23480041/is-the-member-field-order-of-a-class-stable
#include <iostream>
#include <algorithm>
#include <stddef.h>

struct Pair {
    int _data[2]; // _data[0] and _data[1] are consecutive,
                  // and _data[0] is at offset 0 (&Pair == &_data[0])
    int _unused[6]; // simulate the compiler inserted some padding here
    int first() { return _data[0]; }
    int second() { return _data[1]; }
    int & operator[] ( size_t x ) { return _data[x]; }
    friend inline bool operator< ( const Pair & lhs, const Pair & rhs ) {
        return lhs._data[0] < rhs._data[0];
    }
    // it is unlikely that the compiler will add any padding to a struct
    // Pair, so sizeof(Pair) == sizeof(_data)
    // however, the standard doesn't preclude it, so we define our own
    // copy constructor and assignment operator to ensure that nothing
    // extraneous is stored
    Pair( const Pair& other ) {
        _data[0] = other._data[0];
        _data[1] = other._data[1];
    }
    const Pair& operator=( const Pair& other ) {
        _data[0] = other._data[0];
        _data[1] = other._data[1];
        return *this;
    }
};

struct Pairs {
    int* _data;
    size_t _size;
    Pairs( int data[], size_t size ) : _data(data), _size(size) {}
    Pair & operator[] ( size_t x ) const {
        return *reinterpret_cast< Pair * >( _data + 2 * x );
    }
    class rai
        : public std::iterator<std::random_access_iterator_tag, Pair>
    {
        int * _p;
        size_t _size;
        size_t _x;
    public:
        rai() : _p(NULL), _size(0), _x(0) {}
        rai( int* data, size_t size )
            : _p(data), _size(size), _x(0) {}
        friend inline bool operator== (const rai& lhs, const rai& rhs) {
            return lhs._p == rhs._p && lhs._x == rhs._x;
        }
        friend inline bool operator!= (const rai& lhs, const rai& rhs) {
            return lhs._p != rhs._p || lhs._x != rhs._x;
        }
        Pair& operator* () const {
            return *reinterpret_cast< Pair * >( _p + 2 * _x );
        }
        rai& operator+=( ptrdiff_t n ) {
            _x += n;
            if (_x >= _size) { _x = _size = 0; _p = NULL; }
            return *this;
        }
        rai& operator-=( ptrdiff_t n ) {
            if (n > _x) _x = 0;
            else _x -= n;
            return *this;
        }
        friend inline rai operator+ ( rai lhs, const ptrdiff_t n ) {
            return lhs += n;
        }
        friend inline rai operator- ( rai lhs, const ptrdiff_t n ) {
            return lhs -= n;
        }
        friend inline bool operator< ( const rai & lhs, const rai & rhs ) {
            return *lhs < *rhs;
        }
        rai& operator++ () { return *this += 1; }
        rai& operator-- () { return *this -= 1; }
        friend inline ptrdiff_t operator-(const rai& lhs, const rai& rhs) {
            return lhs._p == NULL
                ? (rhs._p == NULL ? 0 : rhs._size - rhs._x)
                : lhs._x - rhs._x;
        }
    };
    inline rai begin() { return rai(_data,_size); }
    static inline const rai end() { return rai(); }
};

int main() {
    int N=5;
    int data[10]= {1,2,7,8,13,14,10,11,4,5};
    Pairs pairs( data, N );

    std::cout << "unsorted" << std::endl;
    for(int i=0; i<N; ++i)
       std::cout << i << ": (" << pairs[i].first() << ", "
                 << pairs[i].second() << ")" << std::endl;

    std::sort(pairs.begin(), pairs.end());

    std::cout << "sorted" << std::endl;
    for(int i=0; i<N; ++i)
        std::cout << i
            << ": (" << pairs[i][0]  // same as pairs[i].first()
            << ", "  << pairs[i][1]  // same as pairs[i].second()
            << ")" << std::endl;

    return 0;
}