Dereferencing type-punned pointer will break strict-aliasing rules

后端 未结 7 1457
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-01 01:05

I used the following piece of code to read data from files as part of a larger program.

double data_read(FILE *stream,int code) {
        char data[8];
              


        
相关标签:
7条回答
  • 2020-12-01 01:09

    Using a union is not the correct thing to do here. Reading from an unwritten member of the union is undefined - i.e. the compiler is free to perform optimisations that will break your code (like optimising away the write).

    0 讨论(0)
  • 2020-12-01 01:09

    This doc summarizes the situation: http://dbp-consulting.com/tutorials/StrictAliasing.html

    There are several different solutions there, but the most portable/safe one is to use memcpy(). (The function calls may be optimized out, so it's not as inefficient as it appears.) For example, replace this:

    return *(short*)data;
    

    With this:

    short temp;
    memcpy(&temp, data, sizeof(temp));
    return temp;
    
    0 讨论(0)
  • 2020-12-01 01:10

    The authors of the C Standard wanted to let compiler writers generate efficient code in circumstances where it would be theoretically possible but unlikely that a global variable might have its value accessed using a seemingly-unrelated pointer. The idea wasn't to forbid type punning by casting and dereferencing a pointer in a single expression, but rather to say that given something like:

    int x;
    int foo(double *d)
    {
      x++;
      *d=1234;
      return x;
    }
    

    a compiler would be entitled to assume that the write to *d won't affect x. The authors of the Standard wanted to list situations where a function like the above that received a pointer from an unknown source would have to assume that it might alias a seemingly-unrelated global, without requiring that types perfectly match. Unfortunately, while the rationale strongly suggests that authors of the Standard intended to describe a standard for minimum conformance in cases where a compiler would otherwise have no reason to believe that things might alias, the rule fails to require that compilers recognize aliasing in cases where it is obvious and the authors of gcc have decided that they'd rather generate the smallest program it can while conforming to the poorly-written language of the Standard, than generate code which is actually useful, and instead of recognizing aliasing in cases where it's obvious (while still being able to assume that things that don't look like they'll alias, won't) they'd rather require that programmers use memcpy, thus requiring a compiler to allow for the possibility that pointers of unknown origin might alias just about anything, thus impeding optimization.

    0 讨论(0)
  • 2020-12-01 01:11

    Apparently the standard allows sizeof(char*) to be different from sizeof(int*) so gcc complains when you try a direct cast. void* is a little special in that everything can be converted back and forth to and from void*. In practice I don't know many architecture/compiler where a pointer is not always the same for all types but gcc is right to emit a warning even if it is annoying.

    I think the safe way would be

    int i, *p = &i;
    char *q = (char*)&p[0];
    

    or

    char *q = (char*)(void*)p;
    

    You can also try this and see what you get:

    char *q = reinterpret_cast<char*>(p);
    
    0 讨论(0)
  • 2020-12-01 01:25

    It looks a lot as if you really want to use fread:

    int data;
    fread(&data, sizeof(data), 1, stream);
    

    That said, if you do want to go the route of reading chars, then reinterpreting them as an int, the safe way to do it in C (but not in C++) is to use a union:

    union
    {
        char theChars[4];
        int theInt;
    } myunion;
    
    for(int i=0; i<4; i++)
        myunion.theChars[i] = fgetc(stream);
    return myunion.theInt;
    

    I'm not sure why the length of data in your original code is 3. I assume you wanted 4 bytes; at least I don't know of any systems where an int is 3 bytes.

    Note that both your code and mine are highly non-portable.

    Edit: If you want to read ints of various lengths from a file, portably, try something like this:

    unsigned result=0;
    for(int i=0; i<4; i++)
        result = (result << 8) | fgetc(stream);
    

    (Note: In a real program, you would additionally want to test the return value of fgetc() against EOF.)

    This reads a 4-byte unsigned from the file in little-endian format, regardless of what the endianness of the system is. It should work on just about any system where an unsigned is at least 4 bytes.

    If you want to be endian-neutral, don't use pointers or unions; use bit-shifts instead.

    0 讨论(0)
  • 2020-12-01 01:31

    Basically you can read gcc's message as guy you are looking for trouble, don't say I didn't warn ya.

    Casting a three byte character array to an int is one of the worst things I have seen, ever. Normally your int has at least 4 bytes. So for the fourth (and maybe more if int is wider) you get random data. And then you cast all of this to a double.

    Just do none of that. The aliasing problem that gcc warns about is innocent compared to what you are doing.

    0 讨论(0)
提交回复
热议问题