__attribute __((__ align__))不使用静态变量

时间:2009-09-23 21:04:28

标签: c memory gcc alignment

这让我疯了好几天。如果我将其声明为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传递(带或不带静态声明),程序正常终止。

我在运行XP专业版的英特尔酷睿2双核处理器上使用MinGw gcc 4.4.0。

4 个答案:

答案 0 :(得分:2)

来自here

  

请注意,对齐属性的有效性可能会受到链接器固有限制的限制。在许多系统上,链接器只能安排变量对齐到某个最大对齐。 (对于某些链接器,支持的最大对齐可能非常小。)如果链接器只能将变量对齐到最多8个字节对齐,那么在__attribute__中指定aligned(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个字节对齐。它足够智能,可以完全删除断言,只打印出1的printf。

但是,在链接阶段,链接器(不像GCC那样)不能保证与16字节对齐,而是选择8(参见上面的引用)。到那时,将断言和非优化的printfs放回到代码中为时已晚。所以实际的可执行文件不会断言(因为它们已被取出)并且它将打印优化的1而不是计算它为运行时。

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

如果情况确实如此,毫无疑问这是我见过的更为狡猾的问题之一。我毫不犹豫地把它称为​​一个错误,因为gcc和ld都是广告宣传的 - 这是因素的组合,搞砸了。

答案 1 :(得分:2)

在我的工作机器上(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(在@Falaina的建议下由@Pax添加 - 我们都建议你接受这个,如果结果是这样的话):

实际上我认为@Falaina在对@ Pax的回答发表评论时将其钉在了一起:

  

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

这是解释。 GCC正在从源代码中搞清楚输入确实(应该是)与16个字节对齐。它足够聪明,可以完全删除assert,只打印出printf的1。

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

  

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

到那时,将assert和非优化printf恢复到代码中为时已晚。所以实际的可执行文件不会断言(因为它们已被取出)并且它将打印优化的1而不是计算它为运行时。

volatile在我的回答中修复它的原因是因为GCC不会优化包含易失性组件的表达式。它会保留assert并在运行时正确计算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;
}

答案 2 :(得分:0)

我想不出有什么好的理由,但是在半小时内你也没有得到任何其他答案,所以我会采用风格投诉和一些猜测和猜疑......

  • 请从第一个&中的地址表达式中取出printf()。虽然(&array)(array)是C中具有相同值的相同表达式,但在两行之间以两种方式写入它似乎是完全错误的。总有一天只关心程序中的小东西会让你免于一个坏错误,或者有人会将操作数的类型更改为一个额外的&amp;制作一个双间接指针。无论如何,风格很重要。我无法证明这一点,但我确信它确实如此。

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

  • 让我们准确了解您在XP上使用的gnu环境,有几种。

答案 3 :(得分:0)

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) { /* ... */ }

并比较你的发现。