【问题标题】:How to align pointer如何对齐指针
【发布时间】:2011-06-12 05:10:17
【问题描述】:

如何将指针对齐到 16 字节边界?

我找到了这段代码,不确定它是否正确

char* p= malloc(1024);

if ((((unsigned long) p) % 16) != 0) 
{
     unsigned char *chpoint = (unsigned char *)p;        
     chpoint += 16 - (((unsigned long) p) % 16);
     p = (char *)chpoint;
}

这行得通吗?

谢谢

【问题讨论】:

  • 什么工具链和平台?对齐是实现的一个属性。另外,你为什么要这样做?你真的是指C++吗?因为您的代码是有效的 C 但不是有效的 C++。
  • stackoverflow.com/questions/4840410/how-to-align-a-pointer-in-c 可能会引起您的兴趣(因为无论如何您似乎都在编写 C)。
  • 不要使用该代码。如果你试图释放p,它会给你带来各种各样的问题。此外,您不再能保证实际拥有多少内存(介于 1009 和 1024 之间)
  • 我有一段代码可以让你做这样的事情(前提是你使用了自定义的free 函数),但是不需要需要外部内存 - - 它只是分配了比您要求的多一点,并在最后释放了它。如果我找到它,我会尽快在这里发布。
  • 我重写并发布了它;尝试使用它,看看它是否有帮助。

标签: c++


【解决方案1】:

C++0x 提出了std::align,它就是这么做的。

// get some memory
T* const p = ...;
std::size_t const size = ...;

void* start = p;
std::size_t space = size;
void* aligned = std::align(16, 1024, p, space);
if(aligned == nullptr) {
    // failed to align
} else {
    // here, p is aligned to 16 and points to at least 1024 bytes of memory
    // also p == aligned
    // size - space is the amount of bytes used for alignment
}

这似乎非常低级。我觉得

// also available in Boost flavour
using storage = std::aligned_storage_t<1024, 16>;
auto p = new storage;

也可以。如果你不小心的话,你很容易违反别名规则。如果您有一个精确的场景(在 16 字节边界处放置 N 个 T 类型的对象?)我想我可以推荐一些更好的东西。

【讨论】:

  • 这是一个旧答案,但有两点:1。typedef std::aligned_storage&lt;1024, 16&gt; storage; 是错误的。如果要使用这种方法,storage 应该被定义为aligned_storage_t&lt;1024,16&gt;aligned_storage&lt;1024,16&gt;::type。 2. 不能保证它适用于过度对齐的类型。如果N &gt; alignof(std::max_align_t),则可能是alignof(aligned_storage_t&lt;1024,N&gt;) &lt; N
  • @Pixelchemist 谢谢,已修复。至于过度对齐的类型,这与std::aligned_storage_t 本身的关系不大,更多的是与它们的实现定义的性质有关。具体做什么最好留给程序员。
【解决方案2】:

试试这个:

它返回对齐的内存并释放内存,几乎没有额外的内存管理开销。

#include <malloc.h>
#include <assert.h>

size_t roundUp(size_t a, size_t b) { return (1 + (a - 1) / b) * b; }

// we assume here that size_t and void* can be converted to each other
void *malloc_aligned(size_t size, size_t align = sizeof(void*))
{
    assert(align % sizeof(size_t) == 0);
    assert(sizeof(void*) == sizeof(size_t)); // not sure if needed, but whatever

    void *p = malloc(size + 2 * align);  // allocate with enough room to store the size
    if (p != NULL)
    {
        size_t base = (size_t)p;
        p = (char*)roundUp(base, align) + align;  // align & make room for storing the size
        ((size_t*)p)[-1] = (size_t)p - base;      // store the size before the block
    }
    return p;
}

void free_aligned(void *p) { free(p != NULL ? (char*)p - ((size_t*)p)[-1] : p); }

警告:

我很确定我在这里踩到了 C 标准的一部分,但谁在乎呢。 :P

【讨论】:

  • 这晚了几年,但有一个很酷的 intptr_t 隐藏在 cstdint 中,它保证可以与指针相互转换,并保证支持算术。
  • @mehrdad 你能在你的代码中加入一些 cmets 吗?还有为什么你使用 (2*align) "void *p = malloc(size + 2 * align);"在 malloc 中,如果我想要 8/16 对齐,上面的代码可以正常工作吗?
  • @eswaat: roundUp(base, align) + align 可以轻松地将base 增加不止align
  • @Mehrdad 抱歉没听明白,您能否详细说明或在您的代码中添加一些 cmets,例如为什么 2*align 等?要求更多,但这是第一次。谢谢。
  • @eswaat:这有帮助吗?
【解决方案3】:

在 glibc 库 malloc 中,realloc 总是返回对齐的 8 个字节。如果您想分配具有更高幂 2 的对齐方式的内存,那么您可以使用memalignposix_memalign。阅读http://www.gnu.org/s/hello/manual/libc/Aligned-Memory-Blocks.html

【讨论】:

    【解决方案4】:

    posix_memalign 是一种方式:http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_memalign.html,只要您的大小是 2 的幂。

    您提供的解决方案的问题是您冒着注销已分配内存的风险。另一种解决方案是分配您想要的大小 + 16 并使用与您正在做的类似的技巧来获得一个对齐的指针,但仍落在您分配的区域内。也就是说,我会使用 posix_memalign 作为第一个解决方案。

    【讨论】:

      【解决方案5】:

      更新:新的更快算法

      不要使用模数,因为在 x86 上它需要数百个时钟周期,因为除法令人讨厌,而在其他系统上则更多。我想出了一个比 GCC 和 Visual-C++ 更快的 std::align 版本。 Visual-C++ 的实现最慢,它实际上使用了一个业余的条件语句。 GCC 与我的算法非常相似,但我的做法与他们的做法相反,但我的算法快了 13.3%,因为它有 13 条而不是 15 条单周期指令。 See here is the research paper with dissassembly。如果您使用掩码而不是 pow_2,则该算法实际上快了一条指令。

      /* Quickly aligns the given pointer to a power of two boundaries.
      @return An aligned pointer of typename T.
      @desc Algorithm is a 2's compliment trick that works by masking off
      the desired number in 2's compliment and adding them to the
      pointer. Please note how I took the horizontal comment whitespace back.
      @param pointer The pointer to align.
      @param mask Mask for the lower LSb, which is one less than the power of 
      2 you wish to align too. */
      template <typename T = char>
      inline T* AlignUp(void* pointer, uintptr_t mask) {
        intptr_t value = reinterpret_cast<intptr_t>(pointer);
        value += (-value) & mask;
        return reinterpret_cast<T*>(value);
      }
      

      这是你的称呼:

      enum { kSize = 256 };
      char buffer[kSize + 16];
      char* aligned_to_16_byte_boundary = AlignUp<> (buffer, 15); //< 16 - 1 = 15
      char16_t* aligned_to_64_byte_boundary = AlignUp<char16_t> (buffer, 63);
      

      这是 3 位的快速逐位证明,它适用于所有位计数:

      ~000 = 111 => 000 + 111 + 1 = 0x1000
      ~001 = 110 => 001 + 110 + 1 = 0x1000
      ~010 = 101 => 010 + 101 + 1 = 0x1000
      ~011 = 100 => 011 + 100 + 1 = 0x1000
      ~100 = 011 => 100 + 011 + 1 = 0x1000
      ~101 = 010 => 101 + 010 + 1 = 0x1000
      ~110 = 001 => 110 + 001 + 1 = 0x1000
      ~111 = 000 => 111 + 000 + 1 = 0x1000
      

      如果您在这里学习如何在 C++11 中将对象与缓存行对齐,请使用in-place constructor

      struct Foo { Foo () {} };
      Foo* foo = new (AlignUp<Foo> (buffer, 63)) Foo ();
      

      这是 std::align 实现,它使用 24 条指令,而 GCC 实现使用 31 条指令,但可以通过将最低有效位的 (--align) 转换为 mask 来调整它以消除递减指令,但是这在功能上与 std::align 不同。

      inline void* align(size_t align, size_t size, void*& ptr,
                         size_t& space) noexcept {
         intptr_t int_ptr = reinterpret_cast<intptr_t>(ptr),
                 offset = (-int_ptr) & (--align);
        if ((space -= offset) < size) {
          space += offset;
          return nullptr;
        }
        return reinterpret_cast<void*>(int_ptr + offset);
      }
      

      使用掩码比使用 pow_2 更快

      这里是使用掩码而不是 pow_2(它是 2 的偶数幂)进行对齐的代码。这比 GCC 算法要胖 20%,但需要您存储掩码而不是 pow_2,因此它不可互换。

      inline void* AlignMask(size_t mask, size_t size, void*& ptr,
                         size_t& space) noexcept {
         intptr_t int_ptr = reinterpret_cast<intptr_t>(ptr),
                 offset = (-int_ptr) & mask;
        if ((space -= offset) < size) {
          space += offset;
          return nullptr;
        }
        return reinterpret_cast<void*>(int_ptr + offset);
      }
      

      【讨论】:

      • 我对新算法非常满意。
      • 两件事:(1)使用优化标志(-O2),如果你做一个基准。 (2) 速度比较不仅仅是比较指令数。
      • 糟糕,感谢您指出这一点。我仍在学习如何进行基准测试。如果所有指令都是单周期的,您可以基于指令计数进行基准测试。我打开了 O2 优化。看起来指令计数现在是 13 到 15 个单周期,不包括跳转,所以它只快了 13.3%,但是这两条额外的指令确实让我领先。但这是使用 pow_2 而不是掩码,所以说 12 到 15 条指令更公平,或者快 20%。
      • 在这种情况下,由于所有指令都是单循环的,带有一个跳转语句,因此基于指令计数进行基准测试会更准确,因为在操作系统上进行基准测试并不准确。如果您有兴趣,我有一篇关于 C++ 基准测试的可爱文章。 github.com/kabuki-starship/kabuki-toolkit/wiki/…
      • 基于指令计数的基准测试只是一个粗略的近似值。如今,处理器非常复杂,它们是流水线的,有乱序执行等。正确测量例程很难,需要经验。那些日子早已一去不复返了,那时我们可以简单地添加周期计数来获得例行程序的整体速度。您的基准文章是朝着正确方向迈出的一步,因为它衡量的是时间。
      【解决方案6】:

      几件事:

      • 不要更改 malloc/new 返回的指针:稍后您将需要它来释放内存;
      • 确保调整对齐后缓冲区足够大
      • 使用size_t 而不是unsigned long,因为size_t 保证与指针具有相同的大小,而不是其他任何东西:

      代码如下:

      size_t size = 1024; // this is how many bytes you need in the aligned buffer
      size_t align = 16;  // this is the alignment boundary
      char *p = (char*)malloc(size + align); // see second point above
      char *aligned_p = (char*)((size_t)p + (align - (size_t)p % align));
      // use the aligned_p here
      // ...
      // when you're done, call:
      free(p); // see first point above
      

      【讨论】:

      • size_t 不能保证与指针具有相同的大小。 (我在 DOS 时间内使用了 16 位 size_t 和 32 位指针),实际上 unsigned long 通常是 size_t 更好的选择(但也没有正式保证,也不能在 Windows 64 位上工作)。 uintptr_t 已为此目的在 C99 和 C++0X 中引入,并且之前通常可作为扩展使用。 C++0X 甚至提供了一种标准的对齐方式(参见 Luc 的回答)
      • 好吧,x86实模式不能和C++ 0x在同一句话中讨论,可以吗?关于实模式长指针,当时的指针运算相当重要,因此无论如何都必须重写。最重要的是,分配函数返回的指针大小取决于所使用的 CRT 库的版本。一些编程环境相应地调整了 size_t 的定义。
      • 为什么不呢? C++0X 的底层抽象机仍然为分段访问而收集,因为它仍然为最奇怪的东西收集,例如字可寻址机器或补码算法。
      猜你喜欢
      • 2011-06-17
      • 2017-12-23
      • 2017-08-16
      • 2015-11-10
      • 2017-10-02
      • 2018-01-17
      • 1970-01-01
      • 2013-09-20
      • 2011-04-16
      相关资源
      最近更新 更多