0

Strict-aliasing has kinda thrown me into a loop. Here's the code.

I have a class

#include <arpa/inet.h>
#include <net/route.h>

class Alias
{
public:
   struct rtentry rt;
   struct sockaddr_in *address;  

   void try_aliasing()
   {
       address = (struct sockaddr_in *)&rt.rt_dst;
       address->sin_family = AF_INET;
   }
};


If I use it like:

int main()
{
   Alias a ;
   a.try_aliasing();
   return 0;
}


It shows:

warning:dereferencing pointer '<anonymous>' does break strict-aliasing rules


However if I use the class as:

int main()
{
   Alias *a = new Alias();
   a->try_aliasing();
   return 0;
}


It compiles just fine.
Compiled both times using:

g++ a.cpp -Wall -O2


Have looked through some threads on strict-aliasing but they've failed to clear the reason for this behavior for me.

underscore_d
  • 6,309
  • 3
  • 38
  • 64
mitgorakh
  • 11
  • 2
  • 3
    I guess the compiler just missed the problem. You are not guaranteed warnings for every type of undefined behaviour, and compilers are not perfect. Also in your second example the pointer a is not even initialized, so even without strict aliasing the program exhibits undefined behaviour. – PaulR Apr 19 '18 at 10:40
  • 4
    Possible duplicate of [What is the C++ compiler required to do with ill-formed programs according to the Standard?](https://stackoverflow.com/questions/15805394/what-is-the-c-compiler-required-to-do-with-ill-formed-programs-according-to-th) - i.e. undefined behaviour, which aliasing violations fall under, does not require a diagnostic. – underscore_d Apr 19 '18 at 10:43
  • Possible duplicate of [will casting around sockaddr\_storage and sockaddr\_in break strict aliasing](https://stackoverflow.com/questions/42178179/will-casting-around-sockaddr-storage-and-sockaddr-in-break-strict-aliasing) – Jean-Michaël Celerier Apr 19 '18 at 10:43
  • In case you were unaware, the right code would be `struct sockaddr *address;` – M.M Apr 19 '18 at 10:54
  • @M.M I am aware of that. However I do actually need to use struct sockaddr_in * in production where a lot more code exists. – mitgorakh Apr 19 '18 at 13:17
  • @mitgorakh it's UB to point a `sockaddr_in *` at a `sockaddr` and dereference it, even if you only access the first member – M.M Apr 19 '18 at 22:43
  • @M.M: It is equally UB to access *any* structure object using the member-access operator unless the member being accessed has a character type. While footnotes are non-normative, the only way 6.5p7 would make any sense would be if it was intended *only* to apply in cases that actually involve aliasing. Note that deriving an lvalue from another and then using the derived lvalue does not involve aliasing unless operations that use the derived lvalue straddle those that don't. – supercat Apr 19 '18 at 23:34

1 Answers1

0

In most situations where compilers would be capable of generating "strict-aliasing" warnings, they could just as easily recognize the relationship between the actions on storage using different lvalues. The reason that the Standard doesn't require that compilers recognize access to storage using lvalues of different types is to avoid requiring them to pessimistically assume aliasing in cases where they would otherwise have no reason to expect it [and would thus have no reason to issue any warnings about it].

As written, the Standard does not recognize any circumstances in which an lvalue of non-character type can be derived from another lvalue and used to access storage as its own type. Even something like:

struct foo {int x;} s = {0};
s.x = 1;

invokes UB because it uses an lvalue of type int to access the storage of an object of type struct foo. The authors of the Standard rely upon compiler writers to recognize situations where common sense would imply that they should behave predictably without regard for whether the Standard actually requires it. I don't think any compiler is so stupid as to not recognize that an operation on s.x is actually an operation on s, and there are many other situations where the authors of the Standard thought compilers would have the sense to recognize such things without being ordered to do so.

Unfortunately, some compiler writers have come to view the rules as justification for assuming that code which looks like it would access storage of one type using lvalues of another, doesn't do so, rather than merely making such assumptions about code that doesn't look like it does so. Given something like:

void doSomehing(structs s2 *p);

void test(struct s1 *p)
{
  doSomething((struct s2*)p};
}

there are at a few ways that something like test might be invoked:

1. It might receive a pointer to a `struct s1`, which `doSomething` will need to operate upon [perhaps using the Common Initial Sequence guarantee] as though it is a `struct s2`.  Further, either:

1a. During the execution of `doSomething` storage accessed exclusively via pointers derived from a struct s2 like the one that was passed in will be accessed exclusively via such means, or...

1b. During the execution of `doSomething` storage accessed via things of that type will also be accessed via other unrelated means.

2. It might receive a pointer to a `struct s2`, which has been cast to a `struct s1*` for some reason in the calling code.

3. It might receive a pointer to a `struct s1`, which `doSomething` will process as though it's a `struct s1`, despite the fact that it accepts a parameter of type `struct s2`.

A compiler might observe that none of the situations whose behavior is defined by the Standard are very likely, and thus decide to issue a warning on that basis. On the other hand, the most common situation by far would be #1a, which a compiler really should be able to handle in predictable fashion, without difficulty, whether the Standard requires it or not, by ensuring that any operations on things of type struct s2 which are performed within the function get sequenced between operations on type struct s1 which precede the call, and those on type struct s1 which follow the function call. Unfortunately, gcc and clang don't do that.

supercat
  • 77,689
  • 9
  • 166
  • 211