Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Strict aliasing rules and function boundaries

+3
−0

Let's analyze this code, assuming an architecture where the alignment of int64_t is the same as that of double:

void
bar(double *f, int64_t *j)
{
    *(int64_t *)f = *j;
}

void
foo(void)
{
    int64_t i = 1;

    bar((double *) &i, &i);
}
  • The object is of type int64_t.
  • The pointer is stored as a pointer to a different object type, but that's allowed by C11::6.3.2.3/7: https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7
  • It's converted back to the actual type of the object, before it's dereferenced.

However, how does this play with restrict (which I didn't use on purpose) and the strict aliasing rules, and assumptions that compilers may make?

Assuming that bar() is defined in a different Translation Unit, the compiler will only see the parameters. With that information, it doesn't know that the real type is correctly accessed, so what can it do? Also, AFAIK, compilers interpret this function prototype so that f and j can not alias, don't they?

Does this code have Defined Behavior?

To be clear, there are two doubts in that code:

  • Is there an implicit restrictness in bar() by the fact that the pointers are pointers to incompatible types, even if I didn't use the qualifier?

  • Is it valid to convert a pointer back to its real type before dereferencing, or is that information lost in the function boundary?

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

1 answer

+3
−0

Assuming that there are no alignment problems between the two pointer types (impl-defined), the code is otherwise well-defined. As per the quoted 6.3.2.3 C allows pretty much any form of wild and crazy pointer conversions by means of a cast, as long as you don't actually de-reference the pointer through the wrong type.

As for the original intention of strict pointer aliasing, it was exactly for cases like this where the compiler should be allowed to assume that double *f and int64_t *j do not alias, especially when the caller is in a different translation unit. But since you overrule all such internal decisions with the explicit cast, the compiler just has to fall in line. And it is fine to convert back to the correct pointer type.

restrict is only an optimizer hint to the compiler and in this case of using the same variable for both arguments, the caller would be violating the "restrict contract" between the caller and a restrict qualified function. I don't know of any compiler which actually checks for restrict violations by the caller.

If you would remove the cast and assignment in the function, then the compiler would still be free to assume that the pointers don't alias even without restrict.

I managed to create a peculiar example with gcc and clang -O3

#include <inttypes.h>
#include <stdio.h>

void
bar(double *f, int64_t * j)
{
    int64_t tmp = *j;
    *f = 1.0;

    if(*j == tmp)
      printf("%"PRIi64 " %"PRIi64 "\n", *j, tmp);
}

int main (void)
{
    int64_t i = 1;

    bar((double *) &i, &i);
    printf("%" PRIi64 "\n", i);
}

Output:

1 1
4607182418800017408

Apparently the compiler assumes:

  • tmp holds the value of *j
  • Thus tmp and *j have the same value, since *j has not been changed.
  • Thus print tmp twice (why load *j from RAM again?).
  • Then on the caller side print the actual value of *j which may have changed by the function with external linkage.

Without -O3 the 1 1 is never printed.

The same thing happens at -O3 if I change the function to:

void
bar(int64_t*restrict f, int64_t*restrict j)
{
    int64_t tmp = *j;
    *f = 2;

    if(*j == tmp)
      printf("%"PRIi64 " %"PRIi64 "\n", *j, tmp);
}

However, if restrict is dropped it does not print 1 1 regardless of optimizations.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »