【问题标题】:__attribute__ ((__aligned__)) not working with static variables__attribute__ ((__aligned__)) 不适用于静态变量
【发布时间】:2010-11-30 21:50:24
【问题描述】:

这几天让我发疯了。如果我将数组声明为static,则无法让数组与 16 对齐。

非常感谢任何帮助。

修订版:

#include <stdio.h>
#include <assert.h>

#define MAX_INPUTS 250

int main()
{
float input[MAX_INPUTS] __attribute__ ((__aligned__(16)));
printf("Address of input: %p\n", input);

printf("Assert1: %x\n", ( ((int) (input))      )        );
printf("Assert2: %x\n", ( ((int) (input)) % 16 )        );
printf("Assert3: %x\n", ( ((int) (input)) % 16 ) == 0   );

assert (     ( ((int) (input))      )        );  
assert (     ( ((int) (input)) % 16 )        );  /* Fails */
assert (     ( ((int) (input)) % 16 ) == 0   );  /* Passes */

return 0;
}

输出是:

Address of input: 0022FB70
Assert1: 22fb70
Assert2: 0
Assert3: 1
Assertion failed: ( ((int) (input)) % 16 ), file aligntest.c, line 16

正如预期的那样,Assert 2 失败,因为地址以 0 结尾。但是,使用:

static float input[MAX_INPUTS] __attribute__ ((__aligned__(16)));

输出是:

Address of input: 00404028
Assert1: 404028
Assert2: 8
Assert3: 1
Assertion failed: ( ((int) (input)) % 16 ), file aligntest.c, line 16

断言 2 仍然失败,尽管结果非零。当 Assert2 被注释掉时,Assert3 通过(有或没有静态声明)并且程序正常终止。

我在 Intel Core 2 Duo 上使用 MinGw gcc 4.4.0,运行 XP Pro。

【问题讨论】:

  • @Ian,查看对@pmg 答案的补充——我想你会发现那里描述的问题已经足够好(以及解决方案,即引入 volatiles 以阻止 gcc 疯狂寻求优化)。
  • @Ian,重新更新:assert2 将 always 失败,原因与 assert3 将 always 通过的原因相同(地址是否实际上由链接器与否)。 GCC 认为它将被对齐并丢弃 assert1/3 并可能将 assert2 优化为 assert(0)。
  • 检查我的(第二个)答案的最新更新
  • @pmg - 我可以看到你在用强制对齐做什么,它有效,所以谢谢你。我将不得不与我的同事(他没有遇到这个问题)讨论这是否是该项目的跑步者。在我们弄清楚为什么我的系统在他工作的地方失败之前,它可能至少必须是一个临时修复。再次感谢

标签: c memory gcc alignment


【解决方案1】:

在我工作的机器(Windows Vista,MinGW gcc 4.3.2)上,您的代码没有为任何优化级别的断言生成任何汇编程序!

为了生成断言,我必须想出一个volatile int 变量并使用-O0 标志进行编译。

int main(void) {
  float input[MAX_INPUTS] __attribute__ ((__aligned__(16)));
  static float input_static[MAX_INPUTS] __attribute__ ((__aligned__(16)));
  volatile int addr_as_int;

  printf("Address of input: %p\n", &input);
  addr_as_int = (int)input;
  print_pointer(input);
  print_int(addr_as_int);
  printf("normal int: %08x; int%%16: %02x\n", addr_as_int, addr_as_int%16);
  printf("Assert: %d\n", (addr_as_int % 16) == 0);
  assert((addr_as_int % 16) == 0); /* Passes */

  printf("Address of input_static: %p\n", &input_static);
  addr_as_int = (int)input_static;
  print_pointer(input_static);
  print_int(addr_as_int);
  printf("static int: %08x; int%%16: %02x\n", addr_as_int, (addr_as_int)%16);
  printf("Assert: %d\n", (addr_as_int % 16) == 0);
  assert((addr_as_int % 16) == 0); /* Does not Pass */

  return 0;
}

我不知道为什么编译器选择从目标文件中删除断言。我快速谷歌搜索没有发现任何有趣的东西。

更新 1(由 @Pax 在 @Falaina 的建议下添加 - 如果事实证明是这样,我们都建议你接受这个):

实际上,我认为@Falaina 在对@Pax 的回答的评论中指出了这一点:

只是一个建议。您是否正在使用优化进行编译?有可能编译器试图变得聪明,然后“嘿,这个变量与 16 个字节对齐,显然地址 % 16 是 0”并将所有检查替换为 1。只是一个想法。

这里是解释。 GCC 正在从源代码中找出输入确实(应该)对齐到 16 个字节。完全删除 asserts 并为 printfs 打印出 1 就足够聪明了。

但是,在链接阶段,链接器无法保证对齐到 16 个字节,而是选择了 8 个字节,因为(来自@Pax):

请注意,对齐属性的有效性可能会受到链接器固有限制的限制。在许多系统上,链接器只能安排变量对齐到某个最大对齐。 (对于某些链接器,支持的最大对齐可能非常小。)如果您的链接器只能将变量对齐到最大 8 字节对齐,那么在 __attribute__ 中指定对齐(16)仍然只会为您提供 8字节对齐。有关详细信息,请参阅您的链接器文档。

到那时将asserts 和未优化的printfs 重新放入代码中为时已晚。所以实际的可执行文件不会断言(因为它们已被取出),它将打印优化的 1 而不是计算它的运行时。

volatile 在我的答案中修复它的原因是因为 GCC 不会优化包含易失性组件的表达式。它将asserts 留在其中并在运行时正确计算printf 参数。


如果您不介意将数组声明为比严格必要的大一点,您可以手动对齐数组:

#include <assert.h>
#include <stdio.h>

#define MAX_INPUTS 250

void *force_align(void *base, size_t s, int align) {
  size_t x;
  int k = 0;
  x = (size_t)base;
  while ((k < align / (int)s) && (x % align)) {
    k++;
    x += s;
  }
  if (k == align) return NULL;
#if 0
  printf("%d elements 'discarded'\n", k);
#endif
  return (void*)((size_t)base + k*s);
}

int main(void) {
  #define ALIGNMENT_REQ 16
  #define EXTRA_ALIGN_REQ (ALIGNMENT_REQ / sizeof (float))
  static float misaligned_input[MAX_INPUTS + EXTRA_ALIGN_REQ]
        __attribute__ ((__aligned__(ALIGNMENT_REQ)));
  float *input;

  /* manual alignment, check for NULL */
  assert( (input = force_align(misaligned_input, sizeof *input, ALIGNMENT_REQ)) );

  printf("Address of misaligned input: %p\n", misaligned_input);
  printf("Address of input: %p\n", input);
  printf("Assert1: %x\n", ( ((int) (input))                 )      );
  printf("Assert2: %x\n", ( ((int) (input)) % ALIGNMENT_REQ )      );
  printf("Assert3: %x\n", ( ((int) (input)) % ALIGNMENT_REQ ) == 0 );
  assert ( ( ((int) (input))                 )      );
#if 0
  assert ( ( ((int) (input)) % ALIGNMENT_REQ )      );  /* Fails */
#endif
  assert ( ( ((int) (input)) % ALIGNMENT_REQ ) == 0 );  /* Passes */

  return 0;
}

【讨论】:

  • @pmg,在这个答案和@Falaina 对我的答案的评论之间,你们已经搞定了。您需要 volatile 的原因 - 没有它,gcc 假定它是对齐的(16)并优化了断言(并且只打印 1)。但是链接器不尊重这一点,尽管到那时更改目标文件为时已晚。有关血腥细节,请参阅我对您答案的更新。
  • 好吧,我没有为我的测试程序进行任何优化编译,但我肯定会想要这样做,所以这是有道理的。我发现很难相信链接器会对齐自动变量,但不是静态的。至少,不是故意的。我试图寻找它支持的对齐方式,但被删除了。这让我想知道是否缺少一些特定于系统的编译设置。我会尝试看看在另一台 PC 上编译时会发生什么。
  • @Ian Shaw - 链接器不负责自动变量的对齐。编译器可以生成保证堆栈变量对齐的程序集(例如,如果我在分配堆栈之前将堆栈指针与 -16 按位和,我已经保证对齐),编译器不会保证全局变量的对齐方式相同(并且静态是实现的作为一天结束时的全局变量),它所能做的最好的事情就是告诉链接器它想要对变量进行某种对齐。
  • 另外,我不知道您是否尝试过 Ian,但您绝对应该尝试使用 volatile 重新运行您的程序并报告结果。如果它在断言中正确失败,那么问题就解决了(尽管您可能在对齐变量方面不走运:(
  • 或者你可以(1)有一个结构数组:“typedef struct {float input;float rubbish} inp_str; inp_str input[250];”并使用 input[].input 确保输入对齐;或 (2) 使您的数组输入 [MAX_INPUTS*2] 并仅使用偶数索引。但是现在我们正在进入严重的混杂领域:-)
【解决方案2】:

来自here

请注意,对齐属性的有效性可能会受到链接器固有限制的限制。在许多系统上,链接器只能安排变量对齐到某个最大对齐。 (对于某些链接器,支持的最大对齐可能非常小。)如果您的链接器只能将变量对齐到最大 8 字节对齐,那么在 __attribute__ 中指定对齐(16)仍然只会为您提供 8字节对齐。有关详细信息,请参阅您的链接器文档。

我知道为什么断言没有发生,这是因为表达式为真 - 不确定为什么表达式为真,但在这种情况下你应该分解它。将这些添加到您的调试语句中:

printf("Assert1: %x\n", ( ((int) (input))));
printf("Assert2: %x\n", ( ((int) (input)) % 16 ));
printf("Assert3: %x\n", ( ((int) (input)) % 16 ) == 0);

并向我们展示结果。

还要检查您正在运行的 gcc 版本 - 4.3.1 和更早版本似乎有一个 problem 对齐。 Cygwin 似乎同时具有 gcc3 和 gcc4 包,假设您正在使用它 - 如果没有,仍然检查版本。

更新 1: 实际上,我认为@Falaina 已经在下面的评论中指出了这一点。这是一个合理的解释。

GCC 正在从源代码中确定输入确实(应该)对齐到 16 个字节。完全删除断言并为 printf 打印出 1 是足够聪明的。

但是,在链接阶段,链接器(能力不如 GCC)无法保证对齐到 16 个字节,而是选择了 8 个字节(请参阅我上面的引用)。到那时,将断言和未优化的 printfs 重新放入代码中为时已晚。所以实际的可执行文件不会断言(因为它们已被取出),它将打印优化的 1 而不是计算它的运行时。

volatile 在@pmg 的答案中修复它的原因是,GCC 将不会优化包含 volatile 组件的表达式。它将断言留在其中并在运行时正确计算打印参数。

如果事实证明是这样,那么这无疑是我见过的更狡猾的问题之一。我不愿称其为错误,因为 gcc 和 ld 都像宣传的那样行事 - 这是把事情搞砸的因素的组合。

【讨论】:

  • Assert1: 404028 Assert2: 8 我正在使用 MinGW,并且刚刚从:gcc (GCC) 4.3.0 20080305 (alpha-testing) mingw-20080502 升级到 gcc (GCC) 4.4。 0 但无济于事。
  • 你得到 8 的 assert2 和 1 的原始断言?请确认,因为那是完全错误的。请添加断言 3 并重新检查。
  • Assert2 失败,即使 printf 报告 8。
     输入地址:00404028 Assert1:404028 Assert2:8 Assert3:1 断言失败:( ((int) (input)) % 16 ) ,文件 aligntest.c,第 25 行
    Assert3 通过(当我注释掉 Assert2 时)
  • 只是一个建议。您是否正在使用优化进行编译?有可能编译器正试图变得聪明并且“嘿,这个变量与 16 个字节对齐,显然地址 % 16 是 0”并将所有检查替换为 1。只是一个想法。
  • 你已经成功了,我相信,@Falaina。我很高兴您将我的更新 1 复制到您自己的答案中并被接受,因为它是您的洞察力使它点击。 @pmg 也值得几票,因为该答案提供了另一个重要的难题。
【解决方案3】:

我想不出任何好的理由,但在 ½ 一个小时内你也没有得到任何其他答案,所以我会带着风格抱怨和一些猜测和怀疑......

  • 请将&amp; 从第一个printf() 中的地址表达式中取出。虽然(&amp;array)(array) 在C 语言中是具有相同值的相同表达式,但在彼此的几行内以两种方式编写似乎是非常错误的。总有一天,只关心程序中的小事情会让你免于犯错误,或者有人会将操作数的类型更改为额外的 & 产生双间接指针的东西。无论如何,风格很重要。我无法证明,但我敢肯定。

  • 让事情变得非常简单。只关注传递了不可能断言的static 存储类的失败案例。创建一个绝对没有任何内容的新目录,仅将失败案例程序复制到该目录,然后根据 CLI 尝试使用./whatever.\whatever 运行它。验证在编译之前没有任何运行。确保你正在运行你认为你正在做的事情。

  • 让我们确切知道您在 XP 上使用的是哪个 gnu 环境,有几种。

【讨论】:

    【解决方案4】:

    pointer to array of floatint 的转换不一定有意义。 如果pointer to array of floats 大于int 你会丢失一些信息,这可能是指针的“低位”。

    试试这个:

    #include <stdio.h>
    #include <string.h>
    
    void print_pointer(void *ptr) {
      unsigned char data[sizeof (void*)];
      size_t k;
    
      memmove(data, &ptr, sizeof (void*));
      printf("ptr: ");
      for (k=0; k<sizeof (void*); k++) {
        printf(" %02x", data[k]);
      }
      puts("");
    }
    

    对 int 执行相同操作

    void print_int(int value) { /* ... */ }
    

    并比较您的发现。

    【讨论】:

    • 我得到两个函数的相同输出。指针:28 40 40 00
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-17
    • 2015-11-27
    • 1970-01-01
    • 2022-01-06
    • 1970-01-01
    • 2020-05-10
    • 1970-01-01
    相关资源
    最近更新 更多