如何在堆栈上对齐缓冲区?

5
我希望实现一个按大小对齐的内存缓冲区,以便我可以使用DMA控制器的模数功能来实现环形缓冲区。我知道可以使用memalign来做到这一点,但我想知道是否可能在堆栈上实现,因为迄今为止我已经能够避免使用动态内存。我正在使用GCC 4.4.1,并且我不关心可移植性(嵌入式系统)。
我想像下面这样做:
template<uint16_t num_channels, uint16_t buffer_size>
class sampler {
    __attribute__((aligned(buffer_size * num_channels * 2)))
    uint16_t buffer[buffer_size][num_channels];
};

当然,GCC不接受非常量对齐方式(而且似乎表明大于8的对齐方式可能无法得到遵守)。

我认为我可以使用C++0x的alignas()来实现这一点,但在GCC版本4.8之前似乎没有出现过它。

我猜一个选项可能是将缓冲区的大小加倍,但那似乎会浪费很多空间(我计划尝试利用设备内存的大部分空间来存储这个缓冲区)。也许我应该放弃并使用动态内存。使用memalign会在浪费空间方面相对有效吗?

有什么想法吗?


你为什么需要这样奇怪的对齐方式(缓冲区大小)?通常所需的对齐方式是基本类型(int,__m128),页面(通常为4KB),或页面分配粒度(在Windows上通常为64KB,不知道您的控制器如何处理)。 - Cory Nelson
可能是因为它简化了DMA操作的封装逻辑。当您的内存还要被基于简单硬件状态机的DMA控制器访问时,额外的限制就会出现。 - Chris Stratton
1
你是否考虑将缓冲区放在自己的翻译单元中,然后使用链接器指令来强制对齐或为该单元设置起始地址?如果您想要它在堆栈上,我认为您需要分配两倍的缓冲区大小。memalign()将使用对齐浪费的空间来满足其他内存分配请求。 - brian beuning
这是在ARM Cortex M4上(实际上是teensy 3)上运行,没有MMU。 - Christopher Mason
1
@brianbeuning 关于“链接器指令”,我猜我得在.ld文件中选择一个缓冲区大小?如果不是,你能否进一步描述一下?我的长期目标之一是制作一个可供他人使用的Arduino库,因此模板非常有吸引力,因为用户可以轻松地在自己的项目中指定大小。 - Christopher Mason
有人知道吗,如果alignas()可用的话(看起来像C++11),它是否会为我实现这个功能?或者它是否会在某个值上达到最大值(例如属性对齐)?顺便说一下,感谢所有的讨论,这真的很有帮助! - Christopher Mason
4个回答

5

你不需要将存储空间大小加倍,只需添加(对齐-1)即可 - 基本上与幕后的memalign执行的操作相同。 对于2的幂次方对齐:

char buf[size + (alignment -1)];
char *aligned = (char*)((intptr_t)buf + (alignment - 1) & ~intptr_t(alignment - 1));

这也是我的想法,但我认为在这种情况下对齐大小与缓冲区大小相同,因此该方法将需要比缓冲区大小少一倍的1。 - Chris Stratton
@Chris 如果这样的话,你一开始就不会浪费太多内存。而且实际上也没有什么可以做的,当你调用它时,sp 将具有特定值,最坏情况下意味着将 alignment - 1 添加到当前值并基本上浪费它。你可以动态计算需要浪费多少来进行对齐,然后在堆栈上动态分配数组,但也仅此而已。 - Voo
@Christopher 不,我确实是指堆栈 (stack)。堆栈上的可变长度数组是 C99 标准的一部分,但不是 C++11 的一部分。但大多数编译器都支持它们的扩展,g++ 显然也是如此 (在这种情况下,VLAs 和固定大小的数组已经引起了同样的问题)。在某些情况下会节省一些内存,但在最坏的情况下,您将不得不浪费 alignment-1 个字节的内存。无论如何都无法避免这个问题。 - Voo
@Voo 哦,我明白你的意思了;我想我已经通过模板来做到这一点了。因此,你建议将缓冲区的大小加倍,然后计算对齐方式。 - Christopher Mason
@Christopher 不,数组的大小(这是您在模板中指定的内容)和函数调用时堆栈指针的实际值之间存在差异。所需的对齐取决于堆栈指针的值。但是,即使您让编译器为您执行对齐操作,它也不能做比您已经做的更多(它可以重新排列本地变量以最小化浪费的空间)。 - Voo
显示剩余2条评论

1

我已经很久没有使用链接器命令文件了,但我认为它应该是这样的。

创建带有文件缓冲区.cpp的文件

char buffer[ BUFFER_SIZE ];

一个对象文件有名为.bss(用于未初始化数据)、.data(用于已初始化数据)和.text(用于可执行代码)的部分。 由于buffer[]未初始化,因此将放在.bss中。

因此,像这样的(gnu)链接器文件应该可以解决问题。

SECTIONS {
   .bss 0x0  : {
        buffer.o(.bss)
        *(.bss)
    }
   .data : {
        *(.data)
    }
   .text : {
        *(.text)
    }
}

0x0 告诉链接器将 buffer[] 加载到地址 0x0。


0
你能否创建一个比buffer_size更大的缓冲区,然后计算一个偏移量以从中开始吗?

0
如果您的嵌入式系统具有内存管理单元,那么在每次运行时只分配一次动态内存,就没有必要担心其谨慎使用。
如果没有内存管理单元,您可以考虑使用链接器映射文件为其分配固定位置。
对于实际操作系统的系统,DMA兼容缓冲区可能需要由内核进行特殊分配。

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