【问题标题】:Aligning data on the stack (C++)对齐堆栈上的数据 (C++)
【发布时间】:2011-07-05 06:55:15
【问题描述】:

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

我正在尝试根据某些任意类型的对齐方式来确定如何对齐堆栈上的字符缓冲区。理想情况下,代码如下:

__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 的东西应该可以工作,我只是语法错误还是什么?

感谢阅读:)

【问题讨论】:

  • ...the __alignof [struct] is the alignment requirement of the largest element in the structure. (msdn.microsoft.com/en-us/library/45t0s5f4(v=vs.71).aspx)
  • 如果您想节省空间并压缩数据,为什么不直接使用 align(1)?
  • 这并不是真正的节省空间,它更多的是在堆栈上为任意类型分配空间,并尊重该类型的对齐要求。第二个代码块就是这样做的,我只是想知道(通常情况下)是否有更好的方法来做到这一点。
  • er,你为什么不按正常的方式在堆栈上分配对象呢?然后编译器会为你对齐。
  • @jalf 这意味着将为缓冲区中的每个元素调用一次类型的构造函数。这就是我要避免的:)

标签: c++ alignment


【解决方案1】:

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

__declspec( align( # ) ) declarator

# 是对齐值。有效条目是 2 的整数幂 1 到 8192(字节),例如 2、4、8、 16、32 或 64. declarator 是数据 您声明为对齐。

-align (C++)

【讨论】:

  • 在我的测试用例中是这样。在一般情况下,它不必是:__alignof( char ) 将返回 1。我想这是我的第一种方法不起作用的充分理由。
  • @JBeFat: 12 的整数幂...它是 2**0。
【解决方案2】:

如果您想在堆栈上声明对齐数据,则无需执行任何额外的“修改”。编译器会处理这个问题。

如果之后你希望它是一个 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 是 文字。它必须是数字,而不是函数调用。

【讨论】:

  • 我更想将数据声明为char,然后再进行转换。我基本上想在堆栈上构造一个特定类型的数组,而不调用它们的构造函数。原则上这就像转换 char 缓冲区一样简单,但我真的希望堆栈上的 alloc 尊重缓冲区将用于的类型的对齐要求。
【解决方案3】:

更新

查看罗伯特·奈特的答案!使用 C++11 但比这更干净...


原答案

这个讨厌的黑客怎么样:

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&lt;T,N&gt; array; 可以出现在代码中,但也可以作为某个更大类的成员(这就是我使用此 hack 的方式),并且在堆上分配时也应该正确运行。

错误修复:

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

【讨论】:

  • 这是一个非常有趣的答案,谢谢 :) 我从来没有想过为每个可能的对齐值手动部分专门化模板。我不太明白的一件事是为什么您必须定义自己的新/删除运算符?我的第一个想法是它可以使数据在堆上分配时正确对齐,但是标准的 new 无论如何都不会为您这样做(因为您有 _declspec(align(_A))在那里)?
  • 我不知道关于 new 运算符的标准是什么,但我在 Visual Studio 2008 中进行的测试似乎表明数据没有必要对齐。 (我刚才重做了测试确认)
  • 是的,我所知道的所有标准 malloc 都在 16 左右对齐。固定,硬编码。虽然有一些扩展。 cf_aligned_malloc
【解决方案4】:

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

在堆栈上分配内存。这是 _alloca 的一个版本,具有 CRT 中的安全增强中所述的安全增强。

对齐行为在编译器之间没有标准化,但是对于这个......

_malloca 例程返回一个指向已分配空间的 void 指针,该指针保证为存储任何类型的对象而适当对齐。

【讨论】:

  • 哇,酷。这就是我要找的。我唯一不明白的是它如何保证空间将适当地对齐以存储 any 类型的对象。我测试了 _malloca,它似乎所做的只是返回 16 字节对齐的指针。如果我有一个 __dllspec( align( 128 ) ) 的结构怎么办?也许我对对齐要求不太了解?
  • 我认为“适当对齐”意味着它足以正确执行。如果您需要比这更粗略地对齐,出于优化原因说,那么我猜 alloca() 不会帮助您。
【解决方案5】:

遇到了同样的问题。 您可以混合一个宏(oh恐怖),以安全的方式结合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)];

【讨论】:

  • 如何回答这个问题? IIUC,OP 正是希望避免明确说明他的类型对齐。
  • @Ofek - 它解决了如果指定错误对齐方式会导致编译器错误的问题。对于一次性解决方案,它似乎比其他 hack 简单一点,而且它仍然是安全的,但如果对齐方式发生变化,显然可能需要修改代码。
【解决方案6】:

您可以同时使用std::aligned_storagestd::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 以及 &lt;tr1/type_traits&gt; 标头。

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

【讨论】:

  • 我没有意识到这一点(或者在我回答时它可能不存在?)。绝对比我的解决方案好!
猜你喜欢
  • 2014-03-11
  • 1970-01-01
  • 2011-06-25
  • 2018-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-08
相关资源
最近更新 更多