如何告诉 GCC 指针参数始终是双字对齐的?

Posted

技术标签:

【中文标题】如何告诉 GCC 指针参数始终是双字对齐的?【英文标题】:How to tell GCC that a pointer argument is always double-word-aligned? 【发布时间】:2012-03-25 09:01:20 【问题描述】:

在我的程序中,我有一个函数可以进行简单的向量加法c[0:15] = a[0:15] + b[0:15]。函数原型为:

void vecadd(float * restrict a, float * restrict b, float * restrict c);

在我们的 32 位嵌入式架构上,有一个加载/存储双字的加载/存储选项,例如:

r16 = 0x4000  ;
strd r0,[r16] ; stores r0 in [0x4000] and r1 in [0x4004]

GCC 优化器识别循环的向量性质并生成代码的两个分支 - 一个用于 3 个数组是双字对齐的情况(因此它使用双加载/存储指令),另一个用于这种情况数组是字对齐的(它使用单个加载/存储选项)。

问题在于地址对齐检查相对于加法部分来说代价高昂,我想通过提示编译器 a、b 和 c 总是 8 对齐来消除它。是否有一个修饰符可以添加到指针声明中来告诉编译器?

用于调用该函数的数组具有aligned(8) 属性,但并不反映在函数代码本身中。是否可以将此属性添加到函数参数中?

【问题讨论】:

即使我下面的代码不能帮助你(因为它是 C++),你可能只想在你的代码中 printf("%p") &array[0] 和 &array[1]确保遵守对齐,并且每个元素 - 不仅仅是在数组起始地址上。 @Joe - 实际上要求它不对齐每个数组元素。它确实必须是一个连续的浮点数组,其原点是 8 对齐的。 【参考方案1】:

如何告诉 GCC 指针参数始终是双字对齐的?

看起来较新版本的 GCC 有 __builtin_assume_aligned:

内置函数:void * __builtin_assume_aligned (const void *exp, size_t align, ...)

此函数返回其第一个参数,并允许编译器假定返回的指针至少是对齐字节对齐的。 这个内置可以有两个或三个参数,如果它有三个, 第三个参数应该是整数类型,如果它是非零的 表示错位偏移。例如:

void *x = __builtin_assume_aligned (arg, 16);

意味着编译器可以假设 x,设置为 arg,至少 16 字节对齐,而:

void *x = __builtin_assume_aligned (arg, 32, 8);

意味着编译器可以假设 x,设置为 arg,即 (char *) x - 8 是 32 字节对齐的。

根据大约 2010 年 Stack Overflow 上的一些其他问题和答案,GCC 3 和早期的 GCC 4 中似乎没有内置。但我不知道截止点在哪里。

【讨论】:

谢谢。此处的几个答案中都提到了它,并且在提出问题时在 GCC 中可用。【参考方案2】:

按照我在我的系统上找到的一段示例代码,我尝试了以下解决方案,它结合了之前给出的一些答案的想法:基本上,创建一个带有 64-位类型 - 在这种情况下是浮点数的 SIMD 向量 - 并使用操作数浮点数组的强制转换调用函数:

typedef float f2 __attribute__((vector_size(8)));
typedef union  f2 v; float f[2];  simdfu;

void vecadd(f2 * restrict a, f2 * restrict b, f2 * restrict c);

float a[16] __attribute__((aligned(8)));
float b[16] __attribute__((aligned(8)));
float c[16] __attribute__((aligned(8)));

int main()

    vecadd((f2 *) a, (f2 *) b, (f2 *) c);
    return 0;

现在编译器不会生成 4-aligned 分支。

但是,__builtin_assume_aligned() 将是更可取的解决方案,它可以防止演员阵容和可能的副作用,只要它有效...

编辑:我注意到内置函数在我们的实现中实际上是错误的(即,不仅它不起作用,而且它会导致代码后面的计算错误。

【讨论】:

【参考方案3】:

如果属性不起作用,或者不是一个选项......

我不确定,但试试这个:

void vecadd (float * restrict a, float * restrict b, float * restrict c)

   a = __builtin_assume_aligned (a, 8);
   b = __builtin_assume_aligned (b, 8);
   c = __builtin_assume_aligned (c, 8);

   for ....

这应该告诉 GCC 指针是对齐的。从那以后它是否做你想要的取决于编译器是否可以有效地使用该信息;它可能不够聪明:这些优化并不容易。

另一种选择可能是将浮点数包装在必须是 8 字节对齐的联合中:

typedef union 
  float f;
  long long dummy;
 aligned_float;

void vedadd (aligned_float * a, ......

我认为这应该强制执行 8 字节对齐,但同样,我不知道编译器是否足够聪明以使用它。

【讨论】:

噢!我刚刚注意到 GCC 手册的下一页 __builtin_assume_aligned。我会编辑答案。 谢谢,@ams。这可能是完美的解决方案。不幸的是,在我们的编译器上,虽然编译得很好,但并不影响输出,编译器仍然会检查指针是否对齐并选择所需的代码路径。 如果有人可以确认它适用于其他架构,我会接受这个答案。 你的联合提案,顺便说一句,不是我想要的,因为它会使数组的每个元素都对齐 8,而我正在处理浮点数组。但是,我可以将两个浮点数打包到一个结构中以以相同的方式工作。 四处转换可能是一个糟糕的计划,因为您可能会遇到别名错误。不过,你没有理由不能拥有union float f[100]; long long dummy :)【参考方案4】:

gcc 版本在简单类型定义和数组上的 align() 方面一直很狡猾。通常,要执行您想要的操作,您必须将浮点数包装在一个结构中,并让包含的浮点数具有对齐限制。

通过运算符重载,您几乎可以轻松完成此操作,但它确实假设您可以使用 c++ 语法。

#include <stdio.h>
#include <string.h>

#define restrict __restrict__

typedef float oldfloat8 __attribute__ ((aligned(8)));

struct float8

    float f __attribute__ ((aligned(8)));

    float8 &operator=(float _f)  f = _f; return *this; 
    float8 &operator=(double _f)  f = _f; return *this; 
    float8 &operator=(int _f)  f = _f; return *this; 

    operator float()  return f; 
;

int Myfunc(float8 * restrict a, float8 * restrict b, float8 * restrict c);

int MyFunc(float8 * restrict a, float8 * restrict b, float8 * restrict c)

    return *c = *a* *b;


int main(int argc, char **argv)

    float8 a, b, c;

    float8 p[4];

    printf("sizeof(oldfloat8) == %d\n", (int)sizeof(oldfloat8));
    printf("sizeof(float8) == %d\n", (int)sizeof(float8));

    printf("addr p[0] == %p\n", &p[0] );
    printf("addr p[1] == %p\n", &p[1] );

    a = 2.0;
    b = 7.0;
    MyFunc( &a, &b, &c );
    return 0;

【讨论】:

谢谢,@Joe。第一,我仅限于 C。第二,我在这里看到的可能问题(与其他建议一样)是,在声明 float8 元素的向量时,每个元素都将是 8 对齐的。这将创建一个不连续的 float-space-float-space 数组等​​。我假设 p[0] 和 p[1] 的 printf() 将揭示这一事实。【参考方案5】:

对齐规范通常只适用于小于指针基类型的对齐,而不是更大的。

我认为最简单的方法是使用对齐规范声明整个数组,例如

typedef float myvector[16];
typedef myvector alignedVector __attribute__((aligned (8));

(语法可能不正确,我总是很难知道将这些__attribute__s放在哪里)

并在整个代码中使用该类型。对于您的函数定义,我会尝试

void vecadd(alignedVector * restrict a, alignedVector * restrict b, alignedVector * restrict c);

这为您提供了额外的间接性,但这只是语法。 *a 之类的东西只是一个 noop,只是将指针重新解释为指向第一个元素的指针。

【讨论】:

谢谢。为什么不把属性放在第一个 typedef 上? @ysap,我根本不知道将数组类型的属性放在哪里。语法很粗糙。 在乔的评论之后,我认为我上面问题的答案是归因于第一个 typedef 将使数组 elements 8 对齐,而不是数组本身(但显然,由于第一个元素的对齐,它对齐)。有意义吗?【参考方案6】:

我没用过,但是有_属性_((aligned (8)))

如果我正确阅读了文档,那么它就是这样使用的:

void vecadd(float * restrict a __attribute__((aligned (8))), 
            float * restrict b __attribute__((aligned (8))), 
            float * restrict c __attribute__((aligned (8))));

见http://ohse.de/uwe/articles/gcc-attributes.html#type-aligned

【讨论】:

除非我错过了,否则 GCC 文档和您链接到的页面提到了变量和函数的 align 属性,但 not 用于函数原型参数。您能指出您链接到的页面中的相关部分吗? 我认为这不会奏效。这告诉编译器指针变量本身是 8 字节对齐的。 我可以确认这不会编译。 error: alignment may not be specified for 我认为参数的 typedef 将清除编译错误。

以上是关于如何告诉 GCC 指针参数始终是双字对齐的?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 GCC 的 32 字节边界处对齐堆栈?

GCC - 如何重新对齐堆栈?

计算机漫游——第三章

如何始终将 UIView 的底部与屏幕底部对齐

C中的字和双字整数

GCC和G ++结构包装的区别?