-3
#include <stdio.h>

int  main() {
  double p = 10.3;
  void *j =  &p;
  *((int*) j) = 2;

  printf("%i: %p\n", *((int *)j), &p);
  printf("%i: %p\n", (int)p, &p);

  return 0;
}

So apparently, I think this is what happens, and I am sure I am not right: Assume that a double is 8 bytes and an int is 4 bytes.
When I cast j to int* and assign it the value 2 via pointer dereference, the left hand side of the assignment basically assigns the first four bytes of j the value 2. Now in the first printf statement, it works as expected, the first four bytes equal the value 2. But in the second printf statement, when I cast p to int, why is it not printing 2? Didn't I overwrite the first four bytes of p via the void pointer and assigned to them the value 2?

So what exactly is happening here? And what is the proper framework required to understand it correctly?

Mat
  • 202,337
  • 40
  • 393
  • 406
User626468
  • 57
  • 5
  • 2
    `works as expected` You are in [undefined behavior](https://en.cppreference.com/w/c/language/behavior) territory after `*((int*) j) = 2;`, so you shouldn't expect anything in particular. – dxiv Jan 02 '21 at 08:11
  • `int` and `double` values have different representations in memory. Not to mention that `sizeof(int)` is usually `4` and `sizeof(double)` usually is `8`. Just the difference in size should make you think. – Some programmer dude Jan 02 '21 at 08:14
  • @dxiv, that document you referred to didn't help much. – User626468 Jan 02 '21 at 08:14
  • 1
    @User626468 You must be a really quick reader ;-) It does, in fact. Btw, I did not downvote the question, but I can see why others did. It would have helped your cause to state upfront that you *know* it is UB, and ask about the particular behavior of the particular implementation you are looking at. – dxiv Jan 02 '21 at 08:16
  • @User626468, one of the points is _access to an object through a pointer of a different type_ in the link shared by dxiv – IrAM Jan 02 '21 at 08:16
  • 1
    @User626468 - I suspect the point of the link is the last sentence of the first bullet-point. *"Compilers are not required to diagnose undefined behavior (although many simple situations are diagnosed), and the compiled program is not required to do anything meaningful."* – enhzflep Jan 02 '21 at 08:16
  • @Someprogrammerdude, I know, and I said that myself. I was basing my question and explanation on this answer by Gilles https://stackoverflow.com/questions/17260527/what-are-the-rules-for-casting-pointers-in-c – User626468 Jan 02 '21 at 08:16
  • 1
    Undefined behavior is undefined. The compiler is allowed to do anything, and anything could happen when running code containing undefined behavior. You could get weird results, you could get crashes, or your house might catch fire. – Some programmer dude Jan 02 '21 at 08:33
  • 2
    I also recommend you learn about [strict aliasing](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule), which you break by pretending a value of one type is something else. – Some programmer dude Jan 02 '21 at 08:33

3 Answers3

1

The program causes undefined behaviour by violating the strict aliasing rule (a double object is written via an expression of incompatible type int).

So the behaviour of the program is not covered by the rules of the language . For further reading see Undefined, unspecified and implementation-defined behavior

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Strictly yes, but on most platforms, the behaviour is defined well, but not portable. The problem is in different binary representations of the "same" values. – 0___________ Jan 02 '21 at 10:10
  • @P__JsupportswomeninPoland the language is defined by a language standard document, not "platforms" – M.M Jan 02 '21 at 10:31
1

So what exactly is happening here?

Since this is UB, you won’t find an answer in the C standard, and thus how do you expect to find an answer while looking at C code? Clearly the C code won’t tell you much. You need to look at the assembly output, and that’s missing from your question, making it incomplete. That probably explains the downvotes: you didn’t provide sufficient information.

But you don’t need to ask us: go to godbolt, choose your compiler, see the assembly output, add an executor and also see the result of running the program, so you can correlate the output with generated assembly. Make sure to select the same compiler version and platform as the one you’ve used to obtain original behavior - don’t forget the optimization level argument to the compiler (aka compiler flag), ie. if there was a -Osomething flag used when you compiled it, the same one is needed to be passed to both the compiler and the executor on godbolt.

Once you get the assembly output - either by providing the -s flag to the compiler on your computer, or by copying it from godbolt, you can then add that to the question, do some work on figuring out what’s going on, and add the real question you got - related to the assembly - if you won’t be able to see how the assembly code produces the output you see.

But don’t ask why the compiler produced such assembly code: UB means that it’s allowed to produce such assembly :) UB is in some ways a carte blanche: the compiler shouldn’t be malicious, but it doesn’t need to be “nice” about it, and thus the generated code may be highly unintuitive.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
0

As other colleagues stated it is the UB. But abstracting from it float has different binary representation than the int. So "2.0f" will be stored in the memory than the "2". You can check it using a very simple program:

#include <stdio.h>
#include <string.h>

float x = 2.0f;
int y = 2;
unsigned char bytes[sizeof(x)];


int main(void)
{

    if(sizeof(y) == sizeof(x))
    {
        memcpy(bytes, &x, sizeof(bytes));

        printf("float %f is in binary: ", x);
        for(size_t i = 0; i < sizeof(bytes); i++)
        {
            printf("%hhx ", bytes[i]);
        }

        
        memcpy(bytes, &y, sizeof(bytes));

        printf("\n\nint %d is in binary: ", y);
        for(size_t i = 0; i < sizeof(bytes); i++)
        {
            printf("%hhx ", bytes[i]);
        }

        memcpy(&x, &y, sizeof(x));

        printf("\n\nfloat %f after integer representation of integer 2 copy\n", x);
    }

}

result:

x86-64 gcc 10.2

Program returned: 0
Program stdout

float 2.000000 is in binary: 0 0 0 40 

int 2 is in binary: 2 0 0 0 

float 0.000000 after integer representation of integer 2 copy

In this program memcpy(&x, &y, sizeof(x)); does exactly the same thing as your pointer assignment, but does it the correct way.

EDIT: Also double version here https://godbolt.org/z/fxccjf

0___________
  • 60,014
  • 4
  • 34
  • 74