3

I've recently learned about strict aliasing in C and have read this post

What is the strict aliasing rule?

but I am confused about when one would not want to enable strict aliasing. I work on embedded C applications with GCC and I've found a number of projects have -fno-strict-aliasing added to the extra flags for a release build but I don't understand why they wouldn't want to enable strict-aliasing for performance. Does anyone have a good example/situation where you wouldn't want to use strict aliasing in C?

Also, does that mean it's not undefined behaviour if we break the strict-aliasing rules but tell the compiler to not do any strict-aliasing optimisations?

Thanks

Nubcake
  • 449
  • 1
  • 8
  • 21

5 Answers5

5

... when one would not want to enable strict aliasing

Strict aliasing is a requirement (or rather a number of requirements) set by the C standard. So C code that are compliant with the standard must obey the (strict) aliasing rules set by the standard.

Compilers can use these rules to optimize the generated code. If some C code violates the standards aliasing rules, the compiler may generate code that behaves unexpectedly due to thus optimizations. Most compilers have an option to turn off optimizations based on aliasing rules.

So the answer is: If your code violates the "strict aliasing" rules, you can tell the compiler not to do optimization based on aliasing rules.

That leads to a new question: Why would you want to write code that violates the aliasing rule?

One reason could be performance. In other words - if you know exactly how your system will behave in case of a violation and your code performs better by doing that violation, you may want to say: I prefer performance above standard compliance.

One example I have seen mentioned is calculation of (network) packet checksums. The structure holding the packet could for instance contain many different fields like mac, ip, etc. but to calculate a checksum of some kind, you might want to view the packet as an array of integers. This is easy to do with code like uint32_t * p = &packet. It violates the aliasing rules but may work fine on the target system as along as the compiler doesn't do optimization based on the strict aliasing rule.

Another reason could be code readability. To avoid violating the aliasing rules you typically need to write "some extra code", e.g. placing things in unions, doing bit-shifts and logical-or to calculate int values and so on. Some find such code less readable and maintainable so they prefer to "write more simple code" by violating the aliasing rules (and therefore switch it off in the compiler).

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • Does this mean that it's not undefined behaviour if the code violates the strict-aliasing rule when the compiler isn't doing any optimisation with regards to strict-aliasing? – Nubcake Sep 08 '20 at 20:42
  • 2
    @Nubcake You can't put it like that. The code is no longer compliant with the C standard so it doesn't really matters what the standard calls UB for aliasing. The compiler will compile the code in accordance with the C standard **except** that the compiler will assume that any pointer may be an alias for any object. – Support Ukraine Sep 08 '20 at 20:46
  • So when is violating strict-aliasing rules undefined behaviour then? – Nubcake Sep 08 '20 at 20:55
  • 1
    @Nubcake It depends on your target system, Even if the C standard say "Doing X" is UB, it may be well defined on one specific target system (e.g. signed integer overflow is UB per standard but most systems handles it in a well defined way). So - as I write in the answer - you got to know the details of your target system and maybe compiler options to know whether it's safe. – Support Ukraine Sep 09 '20 at 04:58
  • @4386427: The code would no longer be Strictly Conforming, but the authors of the Standard have expressly stated that they did not wish to demean code that was useful but non-portable. – supercat Sep 11 '20 at 22:42
2

Some code, like that generated by SWIG for generating bindings between C and many scripting languages, violates all the aliasing rules. It must be compiled with the no strict aliasing flag or nothing works.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
1

Ideally you would use strict aliasing always. Unfortunately aliasing rules are complicated. As a result, most code is not written with strict aliasing in mind, and would break if that optimization would be enabled. Look at any C project and you are likely to find some fishy pointer cast followed by dereference.

Reality is that it's safest to not use strict aliasing for most of the code, and only enable it for the most performance critical code that you have yourself verified to be correct.

user694733
  • 15,208
  • 2
  • 42
  • 68
  • 2
    This answer would be improved if you showed an example of one of these "fishy pointer cast". – Barmar Sep 08 '20 at 15:36
  • 1
    *Unfortunately aliasing rules are complicated.* Not really. You can refer to an object as what it actually is, or as an array of `[[un]signed] char`. – Andrew Henle Sep 08 '20 at 15:42
  • 1
    @AndrewHenle: The rules end up with a lot of corner cases in scenarios involving either heap storage or unions, where they effectively require that the lifetimes of objects be treated separately from the lifetimes of the storage in which they reside, and so far as I can tell every compiler either treats as defined many more useful access patterns than the Standard requires (true of commercial compilers), or fails to reliably handle all the corner cases where the Standard is unambiguous (clang and gcc). – supercat Apr 26 '21 at 21:18
1

Contrary to what is claimed above, strict aliasing is NOT required by any version of the C standard but is a choice by some compilers. And not necessarily a good choice. The reason Linux turns off strict aliasing is that it interferes with standard C programming methods used in the Linux kernel and elsewhere, notably with so-called "type punning". The obvious example is for any type of memory allocator where a freed object of potentially any type is then allocated again as any type. E.g. "x = mymalloc(100); ... myfree(x); .... y = myalloc(100);"Here, myfree gets an object of some type, say, an array of 100 characters, puts it onto an internal queue of freed memory, say as some linked list element, and then may even combine it with adjacent free memory to allocate it as part of a different object, say an array of 200 floats. The ANSI version of the K&R book has an example of an implementation of malloc that is incompatible with strict aliasing and Dennis Ritchie argued, persuasively, that this type of compiler "optimization" was both dangerous and of limited utility.

Of course, I could be wrong - any of the people claiming strict aliasing is mandatory are welcome to cite the provision of some C standard that mandates strict aliasing. They won't be able to do more than cite a provision in which aliasing of some sort is defined to be "undefined behavior" which, according to some interpretations of the current standard permits compilers to use something like strict aliasing.

  • It may be useful to distinguish between requirements for *strictly conforming* C programs, versus the requirements for "conforming" C programs. Adherence to N1570 6.5p7, even in cases where its demands would be unreasonable, is required to make a program *strictly* conforming, but the authors of the Standard used the term *strictly* in an effort to avoid demeaning programs that were useful but non-portable. The fact that compiler writers who aren't interested in paying customers pretend that "non-portable" is synonymous with "broken". Further, the fact that freely-distributable compilers... – supercat Apr 26 '21 at 21:15
  • ...become popular by virtue of being freely distributable doesn't mean that behaviors which are contrary to those of all commercial compilers should be viewed as "mainstream" rather than aberrant. – supercat Apr 26 '21 at 21:16
  • Incidentally, type-based aliasing could be useful and safe if the rules were understood as saying that an lvalue used to access an object has to have a fresh visible relationship to one of the indicated types. In fact, if a compiler made a reasonable effort to recognize such relationships, very little code would need to exploit the "character-type exception". Unfortunately, the authors of the Standard regarded the notion that a compiler writer would be willfully blind to cross-type pointer derivations that would be easy to recognize that they saw no need to forbid such obtuseness. – supercat Apr 26 '21 at 21:22
  • 2
    The standard is exceptionally broken on this issue. The character pointer exception was a hack introduced after the committee realized they had just made it impossible to write memmove in C (so they just mandated it would be inefficient). But the compilers are free to compile type-puns properly and choose not to. – Victor Yodaiken May 02 '21 at 13:55
  • Unfortunately, for whatever reason, the authors of C89 seem to have gone out of their way to avoid suggesting that some C implementations might be better than others, and that it might be worthwhile for programmers to write code that was portable among most but not all implementations. Otherwise, the Standard could have been much more useful if it had identified things that implementations should do when practical, and that programmers should generally expect that implementations will do absent any documented and/or obvious reasons why they would do otherwise (e.g. if a target platform... – supercat May 02 '21 at 16:00
  • ...can't support static objects [true of the environments in which some plug-ins execute] it may be more useful to say that a C code targeting that environment can't use static objects than to simply say that the environment can't run C code). Likewise, if a platform can address six-bit objects, it may sometimes be more useful to have a C implementation with six-bit `char` than one that is limited to allocating memory in twelve-bit chunks. – supercat May 02 '21 at 16:06
0

Given that maintainers of clang and gcc make no effort to handle all of the aliasing corner cases mandated by the Standard (they regard some tricky corner cases they don't handle as being defects in the Standard rather than their compilers), nor to precisely specify what subset of cases they should or should not be expected to handle reliably, I would advise writing code in such a way as to minimize any loss of performance that would result from disabling aliasing, and then documenting a requirement that code be processed with -fno-strict-aliasing. Otherwise, even if code seems to work today, there would be no way of knowing whether the authors of clang and gcc would will perceive any obligation to avoid having future versions break it.

Note that many of the corner cases where clang and gcc fall down involve things like writing storage with data of a new type whose bit pattern happens to match what was already there. In such cases, clang and gcc are prone to optimize out the write without remembering that the write--as written--changed the Effective Type of the storage in question. To be sure, in most cases where code might write data whose bit pattern matches what the storage already held, clang and gcc wouldn't optimize out the write and lose the change to the Effective Type, but I am unaware of any specification as to when they would be guaranteed not to do so.

Worse, some corner cases involve situations where the fact that code might violate gcc's interpretation of the "Strict Aliasing Rule" if executed is sufficient to cause it to jump the rails, even if the code wouldn't be executed.

For example, given the function:

long test(long *p1, long long *p2, int mode)
{
    *p1 = 1;
    if (mode)
        *(long*)p2 = 2;
    else
        *p2 = 2;
    return *p1;
}

x86-64 gcc 10.2 will generate code that, even if mode is 1, will ignore the possibility that storing the value 2 to *(long*)p2 might affect the value of p1.

supercat
  • 77,689
  • 9
  • 166
  • 211