When strict aliasing rule violation is fine? - c

According to:
MISRA C-2012 Rule 11.3 violation while trying to do a typecast from char to int pointer
below piece of code is violating strict aliasing rule but what I'm missing here is what could compiler do wrong in such example that the result would be unexpected/undefined? Also no warning from compiler about any issue.
// compiled with -fstrict-aliasing -Wstrict-aliasing=2 -O3 -std=c99
uint8_t buffer[4];
static uint32_t get_word(uint8_t *cp)
{
return *((uint32_t *)cp);
}
int main()
{
buffer[0] = 8;
printf("value: %u", get_word(buffer));
return 0;
}
Let's not talk why I want to convert buffer of 4 chars to a word like that(instead of shifting) but rather why is this a problem at all and if there are cases where violating this rule will not get me into trouble.

Related

Inconsistent strict aliasing rules

I have the following program where I initialize two buffers in a seemingly fast way by casting the 8-bit buffer to 32 and 64-bit values.
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint32_t a[2];
uint16_t b[4];
} ctx_t;
void inita(ctx_t *ctx, uint8_t *aptr)
{
*(uint64_t *) (ctx->a) = *(uint64_t *) (aptr);
}
void initb(ctx_t *ctx, uint8_t *bptr)
{
for (int i = 0; i < 2; i++) {
*((uint32_t *) (ctx->b) + i) = *(uint32_t *) (bptr);
}
}
int main()
{
uint8_t a[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
uint8_t b[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
ctx_t ctx;
inita(&ctx, a);
initb(&ctx, b);
printf("a: ");
for (int i = 0; i < 8; i++) {
printf("%02x", a[i]);
}
printf("\nb: ");
for (int i = 0; i < 8; i++) {
printf("%02x", b[i]);
}
printf("\n");
}
When compiling using GCC version 8.2.1, I get the following warning message:
> gcc -std=c99 -Wall -Wextra -Wshadow -fsanitize=address,undefined -O2 main.c
main.c: In function ‘inita’:
main.c:11:3: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
*(uint64_t *) (ctx->a) = *(uint64_t *) (aptr);
^~~~~~~~~~~~~~~~~~~~~
I read about the strict aliasing rules and it makes sense that something could go wrong by breaking this.
However, why am I not getting the same warning in the initb() function? Here I am also casting pointers and buffers to other sizes than what was declared.
The program works and yields the expected output:
a: 0123456789abcdef
b: 0123456789abcdef
If I fix the warning, by doing:
void inita(ctx_t *ctx, uint8_t *aptr)
{
*(uint32_t *) (ctx->a) = *(uint32_t *) (aptr);
*(uint32_t *) (ctx->a + 1) = *(uint32_t *) (aptr + 4);
}
Then, I now get the same result as before but without warnings.
Do I still have aliasing problems in my code (due to initb) or is it safe to do this?
Do I still have aliasing problems in my code (due to initb) or is it safe to do this?
Yes there is aliasing problems in both of your functions, and no it is not safe. There are no guarantees it will work or keep working. Compiler simply got confused when trying to detect it.
You need to explictly disable strict aliasing for not to risk compiler generating incorrect code. You also haven't taken care of potential alignment issues. When you fix those, you have also made your code harder to port.
Use memcpy instead:
void inita(ctx_t *ctx, uint8_t *aptr)
{
memcpy(ctx->a, aptr, sizeof uint64_t);
}
In the languages Dennis Ritchie invented, called C, and described in both editions of The C Programming Language (but extended to include 64-bit types) the one problem with the above code is that there is no guarantee that the automatic char[] objects will be aligned in a fashion suitable for access via 64-bit pointer. This would not be a problem on x86 or x64 platforms, but would be a problem on platforms based on other architectures like the ARM Cortex-M0.
Dialects processed by implementations such as MSVC or icc that uphold the Spirit of C described by the Standards Committee, including the principle "Don't prevent the programmer from doing what needs to be done", and make an effort to support low-level programming, will recognize that a construct of the form *(T*)pointerOfTypeU might access an object of type U. Although the Standard doesn't mandate such support, that's likely because the authors expected that implementations would make at least some effort to to recognize situations where a pointer of one type was formed from an lvalue of another type, and thought such recognition could be left as a quality-of-implementation issue. Note that there is nothing in the Standard that would require that a compiler given:
struct foo {int x[10]; };
void test(struct foo *p, int i)
{
struct foo temp;
temp = *p;
temp.x[i] = 1;
*p = temp;
}
recognize that an object of type struct foo might be affected by the actions of forming an int* with the address foo.x+i, dereferencing it, then writing to the resulting lvalue of type int. Instead, the authors of the Standard rely upon quality implementations to make some effort to recognize obvious cases where a pointer or lvalue of one type is derived from a pointer or lvalue of another.
The icc compiler, given:
int test1(int *p1, float *q1)
{
*p1 = 1;
*q1 = 2.0f;
return *p1;
}
int test2(int *p2, int *q2)
{
*p2 = 1;
*(float*)q2 = 2.0f;
return *p2;
}
int test3(int *p3, float volatile *q3)
{
*p3 = 1;
*q3 = 2.0f;
return *p3;
}
will assume that because p1 and p2 are different types, and have no discernible relationship, they will not alias, but will recognize that because p2 and q2 have the same type, they could identify the same object. It will further recognize that the lvalue *(float*)q2 is quite obviously based on q2. Consequently, it will recognize that an access to *(float*)q2 might be an access to *p2. Additionally, icc will treat volatile as a "don't assume you understand everything that's going on here" indication, and thus allow for the possibility that an access via q3 might affect other objects in weird ways.
Some compilers like clang and gcc, unless forced via -O0 or -fno-strict-aliasing to behave in a manner suitable for low-level programming, interpret the Standard as an excuse to ignore obvious relationships among lvalues of different types except in cases where doing so would break language constructs so badly as to make them pretty much useless. Although they happen to recognize that an access to someUnion.array[i] is an access to someUnion, they do recognize *(someUnion.array+i) likewise, even though the definition of someUnion.array[i] is *(someUnion.array+i). Given that the Standard treats support for almost anything having to do with mixed-type storage as a "quality of implementation" issue, all that can really be said is that compilers that are suitable for different purposes support different combinations of constructs.

GCC: Casting const pointers to const pointer of array typedef with -Wcast-qual throws warning

EDIT: problem explained more in depth here (thank you #Eric Postpischil). It seems to be a bug in GCC.
First, let me start with some context: the code I'm writing is using an API I can't change, on a GCC version I can't change, with compilation flags I'm not allowed to remove, and when I'm done with it must have precisely zero warnings or #pragmas.
EDIT: no unions either.
EDIT2: assume the build system also uses -Wall -ansi -pedantic and every other warnings under the sun.
I'll confirm the GCC version tomorrow but I'm fairly certain it's not above GCC 7. In the meantime I'm testing with GCC 6.3.
EDIT3: I'm marking the issue as 'answered'. For completeness' sake, I'm adding some more information below:
I've checked the compiler version being used, and it's not pretty. We're using Mingw and a gcc.exe --version tells me it's GCC 3.4.5.
Furthermore, compilation flags include wall wextra wcast-qual wpointer-arith wconversion wsign-conversion along with others that are not relevant to the problem at hand.
The problem
Consider the following code:
#include "stdio.h"
#include "stdint.h"
typedef uint32_t MyType[4];
const MyType* foo(const uint8_t* a)
{
return (const MyType*) a;
}
void myapi_foo(const MyType* d) {}
int main()
{
uint8_t a[4*sizeof(uint32_t)];
const MyType* b = foo((const uint8_t*) a);
myapi_foo(b);
return 0;
}
Compiled with GCC and the -Wcast-qual flag, this code will throw the following warning:
warning: cast discards ‘const’ qualifier from pointer target type [-Wcast-qual]
return (const MyType*) a;
EDIT: to clarify, the error is on this line:
return (const MyType*) a;
The cause of the problem
I know the root cause of the problem is the typedef type MyType which is in fact an array. Sadly, I do not have the luxury of modifying this typedef, nor the API function myapi_foo and its dubious choice of parameter type.
To be honest, I don't really understand why is the compiler so unhappy about this cast, so clarifications are more than welcome.
The question
What would be the cleanest way of indicating to the compiler everything should be treated as a pointer to const data?
Discarded and potential solutions
Here are a few 'solutions' that I have found but left me unsatisfied:
Remove the -Wcast-qual flag. I cannot do that due to code quality rules.
Add a #pragma to turn off the warning around that part of the code (as shown here). Similarly I'm not allowed to do that.
Cast the pointer to an integer, then cast back to a pointer (as shown here) return (const MyType*) (uint32_t) a;. It's very crude, but using uint32_t as memory addresses has precedent in this project so I might have to use it as a last ditch effort.
EDIT: #bruno suggested using an union to side-step the problem. This is a portable and fairly elegant solution. However, the aforementioned code quality rules downright bans the use of unions.
EDIT: #Eric Postpischil and #M.M suggested using a (const void*) cast return (const void*) a;, which would work regardless of the value of sizeof(MyType*). Sadly it doesn't work on the target.
Thank you for your time.
This is GCC bug 81631. GCC fails to recognize the cast to const MyType * retains the const qualifier. This may be because, in this “pointer to array of four const uint32_t”, GCC performs a test of whether the array is const whether than of whether the array elements are const.
In some GCC versions, including 8.2, a workaround is to change:
return (const MyType*) a;
to:
return (const void *) a;
A more drastic change that is likely to work in more versions is to use:
return (const MyType *) (uintptr_t) a;
Note About Conversion and Aliasing:
It may be a problem that this code passes a to a function that casts it to const MyType *:
uint8_t a[4*sizeof(uint32_t)];
const MyType* b = foo((const uint8_t*) a);
In many C implementations, MyType, being an array of uint32_t, will require four-byte alignment, but a will only require one-byte alignment. Per C 2018 6.3.2.3 6, if a is not correctly aligned for MyType, the result of the conversion is not defined.
Additionally, this code suggests that the uint_t array a may be used as an array of four uint32_t. That would violate C aliasing rules. The code you show in the question appear to be a sample, not the actual code, so we cannot be sure, but you should consider this.
You can do that :
const MyType* foo(const uint8_t* a)
{
union {
const uint8_t* a;
const MyType* b;
} v;
v.a = a;
return v.b;
}
w.c being your modified file :
pi#raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi#raspberrypi:/tmp $
That works whatever the compiler (no #pragma) or the respective size of int and pointer(no cast between int and pointer), but I am not sure this is very elegant ;-)
It is strange to have that foo function and at the same time compile with Wcast-qual, it's contradictory
Edit, If you cannot use union you can also do that
const MyType* foo(const uint8_t* a)
{
const MyType* r;
memcpy(&r, &a, sizeof(a));
return r;
}
Compilation :
pi#raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi#raspberrypi:/tmp $
If nothing works, you might like to use the uintptr_t hammer, if the implmentation provides it. It is optional by the C11 Standard:
const MyType* foo(const uint8_t* a)
{
uintptr_t uip = (uintptr_t) a;
return (const MyType*) uip;
}

Any gcc options to allow ((int *)p)++

Some C compilers can sometimes deduce a casted pointer as still a lvalue, but gcc defaults won't compile it. Instead of having to port (by human-error-prone way)legacy code base to:
p=(int *)p+1 ; /* increment by sizeof(int) */
can gcc be made to allow this code below (even if not technically correct)?
void f() {
void *p ; /* type to be cast later for size */
((int *)p)++ ; /* gcc -c f.c => lvalue required error */ }
Edit: even if technically incorrect, I assume the only programmer intent for such code is for p to remain lvalue and "increment" it, generating same code as my long form, right? (perreal flagged as "lvalue cast")
Edit2: We all agree refactoring to std C is best, but if something like Mateo's -fpermissive worked, it might not catch future programming errors, but hoping initial gcc porting effort will be less faulty... Any other similar suggestions?
If you want to clean up your code(remove excessive casts) , you can concentrate the ugly casts inside a macro or, even better, an inlined function.
The below fragment only makes use of implicit casts to/from void*
You are ,of course, still responsible for the proper alignment.
#include <stdio.h>
static void *increment(void * p,size_t offset)
{
char *tmp = p;
return tmp+offset;
}
int main(void)
{
void *ptr = "Hell0 world!\n";
ptr = increment( ptr, sizeof(int) );
printf("%s", (char*) ptr);
return 0;
}

Why are no strict-aliasing warnings generated for this code?

I have the following code:
struct A
{
short b;
};
struct B
{
double a;
};
void foo (struct B* src)
{
struct B* b = src;
struct A* a = (struct A*)src;
b->a = sin(rand());
if(a->b == rand())
{
printf("Where are you strict aliasing warnings?\n");
}
}
I'm compiling the code with the following command line:
gcc -c -std=c99 -Wstrict-aliasing=2 -Wall -fstrict-aliasing -O3 foo.c
I'm using GCC 4.5.0. I expected the compiler to print out the warning:
warning: dereferencing type-punned pointer will break strict-aliasing rules
But it never is. I can get the warning to be printed out for other cases, but I'm wondering why, in this case, it isn't. Is this not an obvious example of breaking the strict aliasing rules?
GCC's docs for -Wstrict-aliasing=2 says (emphasis mine):
Level 2: Aggressive, quick, not too
precise. May still have many false
positives (not as many as level 1
though), and few false negatives (but
possibly more than level 1). Unlike
level 1, it only warns when an address
is taken. Warns about incomplete
types. Runs in the frontend only.
It seems like your code isn't too tricky, so I'm not sure why there'd be a false negative, but maybe it's because you don't use the & address-of operator to perform the aliasing (that might be what's meant by "only warns when an address is taken")
Update:
It is from not using the address-of operator. If I add the following code to the foo.c file:
int usefoo(void)
{
struct B myB = {0};
foo( &myB);
return 0;
}
The warning is issued.
If usefoo() is in a separate compilation unit, no warning is issued.

Performance benefits of strict aliasing

In C, what exactly are the performance benefits that come with observing strict aliasing?
There is a page that describes aliasing very thoroughly here.
There are also some SO topics here and here.
To summarize, the compiler cannot assume the value of data when two pointers of different types are accessing the same location (i.e. it must read the value every time and therefore cannot make optimizations).
This only occurs when strict aliasing is not being enforced. Strict aliasing options:
gcc: -fstrict-aliasing [default] and -fno-strict-aliasing
msvc:
Strict aliasing is off by default.
(If somebody knows how to turn it on,
please say so.)
Example
Copy-paste this code into main.c:
void f(unsigned u)
{
unsigned short* const bad = (unsigned short*)&u;
}
int main(void)
{
f(5);
return 0;
}
Then compile the code with these options:
gcc main.c -Wall -O2
And you will get:
main.c:3:
warning: dereferencing type-punned
pointer will break strict-aliasing
rules
Disable aliasing with:
gcc main.c -fno-strict-aliasing
-Wall -O2
And the warning goes away. (Or just take out -Wall but...don't compile without it)
Try as I might I could not get MSVC to give me a warning.
The level of performance improvement that will result from applying type-based aliasing will depend upon:
The extent to which code caches things in automatic-duration objects, or via the restrict qualifier, indicates that compilers may do so without regard for whether they might be affected by certain pointer-based operations.
Whether the aliasing assumptions made by a compiler are consistent with what a programmer needs to do (if they're not, reliable processing would require disabling type-based aliasing, negating any benefits it could otherwise have offered).
Consider the following two code snippets:
struct descriptor { uint32_t size; uint16_t *dat; };
void test(struct descriptor *ptr)
{
for (uint32_t i=0; i < ptr->size; i++)
ptr->dat[i] = 1234;
}
void test2(struct descriptor *ptr)
{
int size = ptr->size;
short *dat = ptr->dat;
for (uint32_t i=0; i < size; i++)
dat[i] = 1234;
}
In the absence of type-based aliasing rules, a compiler given test1() would have to allow for the possibility that ptr->dat might point to an address within ptr->size or ptr->dat. This would in turn require that it either check whether ptr->dat was in range to access those things, or else reload the contents of ptr->size and ptr->dat on every iteration of the loop. In this scenario, type-based aliasing rules might allow for a 10x speedup.
On the other hand, a compiler given test2() could generate code equivalent to the optimized version of test1() without having to care about type-based aliasing rules. In this case, performing the same operation, type-based aliasing rules would not offer any speedup.
Now consider the following functions:
uint32_t *ptr;
void set_bottom_16_bits_and_advance_v1(uint16_t value)
{
((uint16_t)ptr)[IS_BIG_ENDIAN] = value;
ptr++;
}
void set_bottom_16_bits_and_advance_v2(uint16_t value)
{
((unsigned char*)ptr)[3*IS_BIG_ENDIAN] = value & 255;
((unsigned char*)ptr)[(3*IS_BIG_ENDIAN) ^ 1] = value >> 8;
ptr++;
}
void test1(unsigned n)
{
for (unsigned i=0; i<n; i++)
set_bottom_16_bits_v1(i);
}
void test2(unsigned n, int value)
{
for (unsigned i=0; i<n; i++)
set_bottom_16_bits_v2(value);
}
If a compiler given set_bottom_16_bits_and_advance_v1 and test1 were--even with type-based aliasing enabled--accommodate the possibility that it might modify an object of type uint32_t (since its execution makes use of a value of type uint32_t*), it would not need to allow for the possibility that ptr might hold its own address. If a compiler could not handle the possibility of the first function accessing a uint32_t without disabling type-based aliasing entirely, however, it would need to reload ptr on every iteration of the loop. Almost any compiler(*), with or without type-based aliasing analysis, which is given set_bottom_16_bits_and_advance_v1 and test2, however, would be required to reload ptr every time through the loop, reducing to zero any performance benefits type-based aliasing could have offered.
(*) The CompCert C dialect expressly disallows the use of character pointers, or any other pointer-to-integer type, to modify the values of stored pointer object, since making allowance for such accesses would not only degrade performance, but also make it essentially impossible to identify all corner cases that would need to be evaluated to guarantee that the behavior of a compiler's generated machine code will match the specified behavior of the source.

Resources