0
#include "stdio.h"  


/* array to store data receeived from CAN Bus */
unsigned char a[8] = {0xCD, 0xEF, 0x12, 0x34, 0x50, 0x00, 0x00, 0x00};

typedef struct {
    unsigned int a:12;
    unsigned int b:12;
    unsigned int c:12;
    unsigned int unused:28;
}test;

test *tptr;

int main(void)
{

    tptr = (test*)( (void*)&a); // is this line braking any aliasing rule

    if(tptr->a == 0xCDE)
    {
        printf("\n data received ok");

    }

    return 0;
}

I recently learned about problems due to pointer aliasing in C. I want to know if the above code is breaking any rules. Could it lead to problems?

I know that the bitfield's order is machine and implementation dependent. My question, however, is regarding pointer aliasing rules, which I want to understand more clearly

a3f
  • 8,517
  • 1
  • 41
  • 46
Basha
  • 17
  • 2
  • This is no quiz site. Why do you think it does not? – too honest for this site Jun 18 '16 at 19:56
  • `#include "stdio.h"` is unusual. Why are you not using `#include `? – melpomene Jun 18 '16 at 19:57
  • 2
    I don't know about aliasing, but I'm pretty sure that `tptr = ...` line has undefined behavior. First, `B x; (A *)(void *)&x` is not guaranteed to do anything sensible (you're only guaranteed to be able to convert from `void *` back to the original type `B *`). Second, even if it works, the struct may have alignment requirements. – melpomene Jun 18 '16 at 19:58
  • In MSVC the test failed. Note that `sizeof(test)` is `12` not `8`. Perhaps you need to pack the struct. – Weather Vane Jun 18 '16 at 20:02
  • 1
    In MSVC even with packed struct, size is still 12. And `tptr->a == 0xFCD` – Weather Vane Jun 18 '16 at 20:08
  • 2
    `tptr = (test *)a;` would not violate the alias rule. But you do have all of the problems mentioned in the other comments: alignment, padding, endianness. Also, since your bitfields sizes sum to 64, you should probably be using `uint64_t` instead of `unsigned int` (unless an `unsigned int` is 64 bits on your system). – user3386109 Jun 18 '16 at 20:09
  • i am working on 32 bit Big Endian ARM micro controller – Basha Jun 18 '16 at 20:12
  • @user3386109 indeed in MSVC using a non-packed `struct` with members of type `uint64_t` reduced its size to 8, but still the same output `0xFCD`. – Weather Vane Jun 18 '16 at 20:17
  • @WeatherVane Yup, if you look at `a` as a 64-bit value on a little-endian processor, it's `0x000000503412efcd`, so the bitfields are `0xfcd`, `0x12e`, and `0x034`. OP shouldn't have that problem on a big endian processor. – user3386109 Jun 18 '16 at 20:24
  • @user3386109 Disagree with "you should probably be using `uint64_t`". "A bit-field shall have a type that is a qualified or unqualified version of `_Bool, signed int, unsigned int`, or some other implementation-defined type." §6.7.2.1 5 Using any other type limits portability and possible invokes UB. Best to use `unsigned` just like OP did. – chux - Reinstate Monica Jun 19 '16 at 02:23

1 Answers1

1

It does break strict aliasing. Some (saner) compilers do deduce that obviously tptr should be allowed to alias a, but this is not guaranteed by the standard. In fact, GCC will not consider tptr to be aliasing a.

Another problem is the alignment of a. The x86 is quite tolerant in that regard, but many other architectures, including the ARM, are not.

Consider (int)&a == 0xFFF02 and sizeof(int) == 4 on architecture that support fetching ints only from addresses that are themselves multiples of sizeof(int). Such an unaligned access would result in a bus error, if you do *(int*)&a or even worse the program accessing the wrong location.

To stay clear of undefined behavior, remember that it is guaranteed that accesses through unsigned char* are possible without alignment or aliasing constraints, not the other way round. Thus use:

test buf;
can_recv((unsigned char*)&buf, sizeof buf);
if(tptr->a == 0xCDE)
{
    printf("\n data received ok");

}

Another portability problem, is that you expect that bitfield to be packed a certain way. The standard doesn't guarantee that however. If you want portable behavior, restrict yourself to the bitfield interface (bitfield.b1 = 1) and don't modify the bitfied by other means.

If portability isn't a concern, make sure to set the appropriate compiler flags that guarantee the bitfield packing you're expecting.

Community
  • 1
  • 1
a3f
  • 8,517
  • 1
  • 41
  • 46
  • 1
    I would suggest that all *sane* compilers should recognize that the act of explicitly casting a pointer should serve as an alarm bell "Hey this stuff will alias" provided that all accesses done with the typecast pointer are done before the next access using the outer type. The rationale describes the rule in terms of pointers where the compiler has no reason to expect aliasing; I've seen no evidence the rule was ever intended to suggest that compilers ignore clear and obvious signs of aliasing--the authors of the Standard likely thought that was so obvious it didn't need stating. – supercat Jun 23 '16 at 18:42