__attribute__ ((__aligned__)) not working with static variables

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-24 14:54:56

问题


This has been driving me nuts for days. I can't get an array to align to 16 if I declare it as static.

Any help greatly appreciated.

Revised Version:

#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;
}

The output is:

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

As one would expect, Assert 2 fails because the address ends in 0. However, with:

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

the output is:

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

Assert 2 still fails, although the result is non-zero. When Assert2 is commented out, Assert3 passes (with or without the static declaration) and the program terminates normally.

I'm using MinGw gcc 4.4.0 on an Intel Core 2 Duo, running XP Pro.


回答1:


On my machine at work (Windows Vista, MinGW gcc 4.3.2) your code didn't produce any assembler for the asserts at any optimization level!

To get the asserts to be generated I had to come up with a volatile int variable and compile with -O0 flag.

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;
}

I have no idea why the compiler chose to remove the asserts from the object file. I quick google search didn't reveal anything interesting.

Update 1 (added by @Pax at suggestion of @Falaina - we all suggest you accept this one if it turns out to be the case):

Actually I think @Falaina has nailed it in a comment to @Pax's answer:

Just a suggestions. Are you compiling with optimizations? It's possible the compiler is trying to be clever and going "Hey, this variable is aligned to 16 bytes, obviously the address % 16 is 0" and replacing all your checks with 1. Just a thought.

Here's the explanation. GCC is figuring out from the source code that input is indeed (supposed to be) aligned to 16 bytes. It's smart enough to the drop the asserts altogether and to just print out 1 for the printfs.

However, at the link stage, the linker is not able to guarantee alignment to 16 bytes, instead opting for 8 because (from @Pax):

Note that the effectiveness of aligned attributes may be limited by inherent limitations in your linker. On many systems, the linker is only able to arrange for variables to be aligned up to a certain maximum alignment. (For some linkers, the maximum supported alignment may be very very small.) If your linker is only able to align variables up to a maximum of 8 byte alignment, then specifying aligned(16) in an __attribute__ will still only provide you with 8 byte alignment. See your linker documentation for further information.

By then it's too late to get the asserts and non-optimized printfs back into the code. So the actual executable will not assert (since they've been taken out) and it will print the optimized 1 rather than calculating it an runtime.

The reason the volatile fixes it in my answer is because GCC will not optimize the expressions that contain volatile components. It leaves the asserts in and calculates the printf arguments at runtime properly.


You can manually align your array if you don't mind declaring it a little bit larger than stricly necessary:

#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:


From here:

Note that the effectiveness of aligned attributes may be limited by inherent limitations in your linker. On many systems, the linker is only able to arrange for variables to be aligned up to a certain maximum alignment. (For some linkers, the maximum supported alignment may be very very small.) If your linker is only able to align variables up to a maximum of 8 byte alignment, then specifying aligned(16) in an __attribute__ will still only provide you with 8 byte alignment. See your linker documentation for further information.

I know why the assert isn't happening, it's because the expression is true - not sure exactly why the expression is true, but you should break it down in cases like this. Add these to your debug statements:

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

and show us the results.

Also check which gcc version you're running - 4.3.1 and earlier appear to have a problem with alignment. Cygwin appears to have both gcc3 and gcc4 packages, assuming that's what you're using - if not, still check the version.

Update 1: Actually I think @Falaina has nailed it in a comment below. Here's a plausible explanation.

GCC is figuring out from the source code that input is indeed (supposed to be) aligned to 16 bytes. It's smart enough to the drop the asserts altogether and to just print out 1 for the printf's.

However, at the link stage, the linker (not as capable as GCC) is not able to guarantee alignment to 16 bytes, instead opting for 8 (see my quote above). By then it's too late to get the asserts and non-optimized printfs back into the code. So the actual executable will not assert (since they've been taken out) and it will print the optimized 1 rather than calculating it an runtime.

The reason the volatile fixes it in @pmg's answer is because GCC will not optimize the expressions that contain volatile components. It leaves the asserts in and calculates the print arguments at runtime properly.

If this turns out to be the case, this is without a doubt one of the more devious problems I've seen. I hesitate to call it a bug since both gcc and ld are acting as advertised - it's the combination of factors that are screwing things up.




回答3:


I can't think of any good reason for this, but in ½ an hour you haven't got any other answers either, so I will go with a style complaint and some guesses and suspicions...

  • Please take the & out of the address expression in the first printf(). While (&array) and (array) are the same expression with the same value in C, it seems somehow quite wrong to write it both ways within a few lines of each other. Someday just caring about the little things in a program will save you from a bad bug, or someone will change the type of the operand to something where an extra & makes a double-indirect pointer. Anyway, style matters. I can't prove it, but I'm sure it does.

  • Make things really simple. Concentrate on just the failure case with the static storage class that passes an impossible assert. Make a new directory that has absolutely nothing in it, copy only the failure case program to that, and then, depending on the CLI, try and run it with ./whatever or .\whatever. Verify that nothing runs until you compile it. Make certain you are running the thing you think you are.

  • Let us know exactly which gnu environment you are using on XP, there are several.




回答4:


The conversion from pointer to array of float to int is not necessarily meaningful. If pointer to array of floats is larger than int you lose some information, which maybe the "low order bits" of the pointer.

Try this:

#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("");
}

Do the same for an int

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

and compare your findings.



来源:https://stackoverflow.com/questions/1468513/attribute-aligned-not-working-with-static-variables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!