3

I've recently found this page:

Making PyObject_HEAD conform to standard C

and I'm curious about this paragraph:

Standard C has one specific exception to its aliasing rules precisely designed to support the case of Python: a value of a struct type may also be accessed through a pointer to the first field. E.g. if a struct starts with an int , the struct * may also be cast to an int * , allowing to write int values into the first field.

So I wrote this code to check with my compilers:

struct with_int {
    int a;
    char b;
};

int main(void)
{
    struct with_int *i = malloc(sizeof(struct with_int));
    i->a = 5;
    ((int *)&i)->a = 8;
}

but I'm getting error: request for member 'a' in something not a struct or union.

Did I get the above paragraph right? If no, what am I doing wrong? Also, if someone knows where C standard is referring to this rule, please point it out here. Thanks.

kashpersky
  • 165
  • 2
  • 8

3 Answers3

6

Your interpretation1 is correct, but the code isn't.

The pointer i already points to the object, and thus to the first element, so you only need to cast it to the correct type:

int* n = ( int* )i;

then you simply dereference it:

*n = 345;

Or in one step:

*( int* )i = 345;

1 (Quoted from: ISO:IEC 9899:201X 6.7.2.1 Structure and union specifiers 15)
Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

2501
  • 25,460
  • 4
  • 47
  • 87
1

You have a few issues, but this works for me:

#include <malloc.h>
#include <stdio.h>

struct with_int {
    int a;
    char b;
};

int main(void)
{
    struct with_int *i = (struct with_int *)malloc(sizeof(struct with_int));
    i->a = 5;
    *(int *)i = 8;
    printf("%d\n", i->a);
}

Output is: 8

Wheezil
  • 3,157
  • 1
  • 23
  • 36
0

Like other answers have pointed out, I think you meant:

// Interpret (struct with_int *) as (int *), then
// dereference it to assign the value 8.
*((int *) i) = 8;

and not:

((int *) &i)->a = 8;

However, none of the answers explain specifically why that error makes sense.

Let me explain what ((int *) &i)->a means:

i is a variable that holds an address to a (struct with_int). &i is the address on main() function's stack space. This means &i is an address, that contains an address to a (struct with_int). In other words, &i is a pointer to a pointer to (struct with_int). Then the cast (int *) of this would tell the compiler to interpret this stack address as an int pointer, that is, address of an int. Finally, with that ->a, you are asking the compiler to fetch the struct member a from this int pointer and then assign the value 8 to it. It doesn't make sense to fetch a struct member from an int pointer. Hence, you get error: request for member 'a' in something not a struct or union.

Hope this helps.

ZeZNiQ
  • 612
  • 7
  • 14