在堆栈上对齐数据 (C++)

12

这个问题针对的是MSVC编译器(特别是2008版),但我也对非编译器特定的答案感兴趣。

我正在尝试弄清楚如何根据某种任意类型的对齐方式,将char缓冲区在堆栈上进行对齐。理想情况下,代码应该如下:

__declspec( align( __alignof(MyType) ) ) char buffer[16*sizeof(MyType)];

很遗憾,这没用。

错误 C2059:语法错误:'__builtin_alignof'

编译器不喜欢嵌套语句。

我唯一的想法是这样做:

char buffer[16*sizeof(MyType)+__alignof(MyType)-1];
char * alignedBuffer = (char*)((((unsigned long)buffer) + __alignof(MyType)-1)&~(__alignof(MyType)-1));

有没有人知道更好的方法?看起来declspec应该能用,是我语法有问题还是什么原因呢?

谢谢阅读:)


1
...__alignof [struct] 是结构体中最大元素的对齐要求。 - user295190
如果您正在尝试节省空间并压缩数据,为什么不只是对齐(1)呢? - user295190
2
这并不是真正关于节省空间的问题,而更多地是关于为任意类型在堆栈上分配空间,并尊重该类型的对齐要求。第二个代码块就是这样做的,我只是想知道是否有更好的方法来实现它(通常情况下是这样)。 - JBeFat
嗯,为什么不按照正常的方式在堆栈上分配对象呢?编译器会为您对齐它。 - jalf
2
@jalf 这意味着类型的构造函数将为缓冲区中的每个元素调用一次。这就是我试图避免的 :) - JBeFat
哦,明白了。 (你可能想将这些信息编辑到问题本身中) - jalf
6个回答

5
您可以使用std :: aligned_storage std :: alignment_of作为替代方案来实现这一目的。
#include <type_traits>

template <class T, int N>
struct AlignedStorage
{
    typename std::aligned_storage<sizeof(T) * N, std::alignment_of<T>::value>::type data;
};

AlignedStorage<int, 16> myValue;

这个在 MSVC 2008 及以上版本中是支持的。如果您需要在其他非 C++11 编译器上进行可移植性开发,可以使用 std::tr1::aligned_storagestd::tr1::alignment_of 还有头文件 <tr1/type_traits>

在上述代码中,AlignedStorage<T>::data 将是一个适合 T 类型大小和 T*N 对齐的 POD 类型(在 MSVC 和 GCC 中是 char[] 数组)。


我之前并不知道(或者可能在我回答的时候还不存在?)这绝对比我的解决方案好! - CygnusX1

3

更新

请查看Robert Knight的回答!他使用了C++11,但比这个方法更加简洁...


原始回答

这个可怕的hack怎么样:

namespace priv {

#define PRIVATE_STATICMEM(_A_) \
    template <size_t size> \
    struct StaticMem<size,_A_> { \
      __declspec(align(_A_)) char data[size]; \
      void *operator new(size_t parSize) { \
        return _aligned_malloc(parSize,_A_); \
      } \
      void operator delete(void *ptr) { \
        return _aligned_free(ptr); \
      } \
    };

    template <size_t size, size_t align> struct StaticMem {};
    template <size_t size> struct StaticMem<size,1> {char data[size];};

    PRIVATE_STATICMEM(2)
    PRIVATE_STATICMEM(4)
    PRIVATE_STATICMEM(8)
    PRIVATE_STATICMEM(16)
    PRIVATE_STATICMEM(32)
    PRIVATE_STATICMEM(64)
    PRIVATE_STATICMEM(128)
    PRIVATE_STATICMEM(256)
    PRIVATE_STATICMEM(512)
    PRIVATE_STATICMEM(1024)
    PRIVATE_STATICMEM(2048)
    PRIVATE_STATICMEM(4096)
    PRIVATE_STATICMEM(8192)

}

template <typename T, size_t size> struct StaticMem : public priv::StaticMem<sizeof(T)*size,__alignof(T)> {
    T *unhack() {return (T*)this;}
    T &unhack(size_t idx) {return *(T*)(data+idx*sizeof(T));}
    const T &unhack() const {return *(const T*)this;}
    const T &unhack(size_t idx) const {return *(const T*)(data+idx*sizeof(T));}
    StaticMem() {}
    StaticMem(const T &init) {unhack()=init;}
};

看起来有点吓人,但你只需要在一些隐藏的头文件中使用它一次。然后你可以按照以下方式使用它:

StaticMem<T,N> array; //allocate an uninitialized array of size N for type T
array.data //this is a raw char array
array.unhack() //this is a reference to first T object in the array
array.unhack(5) //reference to 5th T object in the array

StaticMem<T,N> array; 可以出现在代码中,也可以作为某个更大类的成员(这就是我使用此技巧的方式),并且在堆上分配时也应该正确地表现。

错误修复:

示例的第6行:char data[_A_] 已更正为 char data[size]


这是一个非常有趣的答案,谢谢 :) 我从未想过为每个可能的对齐值手动部分地专门化模板。其中一件我不太明白的事情是为什么您不得不定义自己的new/delete运算符?我的第一个想法是在堆上分配数据时正确对齐,但是标准的new不会为您完成吗(因为您已经在__declspec(align(A))中使用)? - JBeFat
我不知道标准对于new运算符有何规定,但是我在我的Visual Studio 2008中进行的测试似乎表明数据并不一定对齐。(我刚刚重新进行了测试以确认) - CygnusX1
是的,我所知道的所有标准malloc都会对齐到16。这是固定的且硬编码的。不过有一些扩展,比如_aligned_malloc - v.oddou

3

你确定MyType是一个有效的整数幂吗?

__declspec( align( # ) ) declarator

# 是对齐值。有效的取值是从 1 到 8192(字节)的二的整数次幂,例如 2、4、8、16、32 或 64。declarator 是你要声明为对齐的数据。

-align (C++)


在我的测试案例中是这样的。在一般情况下则不一定: __alignof(char)将返回1。我猜这已经足够解释为什么我的第一种方法没有起作用了。 - JBeFat
2
@JBeFat:12 的整数次幂... 它是 20 次方。 - Ben Voigt

1

我有同样的问题。 你可以混合使用宏(天哪),以安全的方式组合align__alignof

// `align` und `__alignof` cannot be combined - hence this workaround where you also have to specify the alignment manually (but checked)
#define ALIGN_FOR_TYPE( TypeName, TypeAlignment )                           \
    const size_t ALIGN_FOR_TYPE_alignOf##TypeName = __alignof(TypeName);    \
    BOOST_STATIC_ASSERT(ALIGN_FOR_TYPE_alignOf##TypeName == TypeAlignment); \
    __declspec( align( TypeAlignment ) )                                    \
/**/

ALIGN_FOR_TYPE(MyStructType, 4) char StructBuffer[sizeof(MyStructType)];

那怎么回答这个问题呢?如果我理解正确,原帖的作者想要避免明确说明他的类型对齐方式。 - Ofek Shilon
@Ofek - 它解决了这个问题,如果您指定了错误的对齐方式,您将会得到编译器错误。对于一次性的解决方案来说,它似乎比其他hack方法更简单,并且仍然是安全的,尽管显然如果对齐方式发生变化可能需要修改代码。 - Martin Ba

1

alloca() 怎么样?(或者更具体地说,对于 MSVC2008,_malloca() 呢?)

在堆栈上分配内存。这是带有安全增强功能的 _alloca 版本,如 CRT 中的安全增强所述。

对于编译器,对齐行为并没有标准化,但对于这个编译器...

_malloca 例程返回指向分配空间的 void 指针,保证适合存储任何类型的对象。


哇,太酷了。这正是我在寻找的东西。唯一让我不明白的是它如何保证空间适合存储_任何_类型的对象。我测试了_malloca,似乎它只返回16字节对齐的指针。如果我有一个结构体是__dllspec( align( 128 ) )怎么办?也许我对对齐要求有些不理解? - JBeFat
1
我认为“适当对齐”意味着它足以进行正确的执行。如果出于优化原因需要进行更粗略的对齐,那么我猜alloca()就无法帮助你了。 - Martin Stone

0

如果您想在堆栈上声明对齐的数据,无需执行任何其他“黑科技”。编译器会自动处理。

如果您希望它之后成为char数组,只需将其转换为char*

请尝试以下测试示例以确认:

#include <stdio.h>

struct UnalignedX {
    int x;
};

__declspec(align(128)) struct AlignedX {
    int x;
};

int main() {
    UnalignedX arr[5];
    AlignedX aarr[5];
    printf("UnalignedX: %x %x\n",arr,arr+1);
    printf("AlignedX: %x %x\n",aarr,aarr+1);
    char *final=(char*)aarr; //this becomes the char array that you asked for
    return 0;
};

在我的电脑上,我得到了输出:

UnalignedX: 14fe68 14fe6c
AlignedX: 14fb80 14fc00

在堆上分配数据时(使用mallocnew),对齐方式非常重要。

__declspec( align( N ))中的N必须是一个字面量。它必须是一个数字,而不是函数调用。


1
我更想将数据声明为char,并在之后进行转换。我基本上想在堆栈上构建一个特定类型的数组,而不调用它们的构造函数。原则上,这就像是将char缓冲区强制转换一样容易,但我真的希望堆栈上的分配能够尊重缓冲区将用于的类型的对齐要求。 - JBeFat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接