0

having this:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

typedef struct {
   int a, b;
} str_t;

int main (void) {

   str_t *abc = (str_t*)((((str_t*)0)->a)-offsetof(str_t,a));

   return 0;
}

I have tried to do the same that does this macro:

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );}) 

The compiler didn't gave any specific error, but the resulting program crashed. Why does not crash the macro as well?

24
  • 1
    Did the compiler crash, or did your program crash? Commented May 6, 2020 at 20:04
  • 2
    What are you trying to do? ((str_t*)0)->a is just asking for trouble. Commented May 6, 2020 at 20:04
  • 1
    ((str_t*)0)->a is accessing memory near 0. The container_of macro never dereferences the 0 pointer, it only uses it for the types. Commented May 6, 2020 at 20:05
  • 1
    It's inside typeof. This happens at compile-time, the value of ((type *)0)->member is never accessed. This construct is used to get the type of the member. Commented May 6, 2020 at 20:10
  • 2
    Re "But you usually say that,", No, noone says the compiler crashed when they mean the program crashed. Fixed the question. (It is possible for the compiler to crash; it's just not likely for well-seasoned products like gcc.) Commented May 6, 2020 at 20:14

1 Answer 1

0

Your real question is really about the difference between

typeof( ((struct Foo*)0)->a )    // Relevant code from the macro.

and

int i = ((struct Foo*)0)->a;     // Relevant code from your program.

Let's start by using a valid pointer p instead of 0, and ask ourselves what the following two snippets do:

struct Foo s = { 0 };
struct Foo *p = &s;

typeof( p->a )

and

int i = p->a;

In the first case, we are trying to get the type of a member of a structure. The value of p is irrelevant; the compiler only needs its type. In fact, the result is computed during compilation, before p is allocated or assigned a value.

In the second case, we are trying to read memory. This memory will be found some location relative to the pointer in p. Different values of p will result in different memory locations being read.


So what happens when we use 0 instead of a valid pointer?

In the first case, we never had a valid pointer. Because typeof is evaluated during compilation, the pointer doesn't even exist when typeof is evaluated. So that means that the following could conceptually work fine:

typeof( ((struct Foo*)0)->a )

And this brings us to the second case.

int i = ((struct Foo*)0)->a;

0 means NULL when used a pointer, and may not be zero at all.

This tries to read memory some number of bytes after NULL. But NULL isn't an address; it's the lack thereof. The concept of reading the memory from an address relative to NULL is flawed, meaningless. Since the concept is meaningless, it can't possible work fine.


What does the standard say for typeof( ((struct Foo*)0)->a )?

I don't know.


What does the standard say for int i = ((struct Foo*)0)->a;?

The C language doesn't define what happens in that situation. We call this undefined behaviour. The compiler is free to do whatever it wants when it encounters it. Commonly, it results in a protection fault (which results in a SIGSEGV signal on unix systems).

$ gcc -Wall -Wextra -pedantic a.c -o a     # OP's program
a.c: In function ‘main’:
a.c:11:11: warning: unused variable ‘abc’ [-Wunused-variable]
    str_t *abc = (str_t*)((((str_t*)0)->a)-offsetof(str_t,a));
           ^~~

$ ./a
Segmentation fault (core dumped)
Sign up to request clarification or add additional context in comments.

15 Comments

according to this stackoverflow.com/questions/57342141/…, ((str_t*)0)->member is not NULL
Please read again. The answer to the linked question says "the behavior of ((size_t)&((type *)0)->member) is not specified by the C standard" just like I did.
But there is difference between "not specified by the C standard" and "SIGSEGV", which is well specified. Now you state both.
Yes, I did state both what the standard says and what usually happens.
Ok, then please explain why can macro container_of use (type *)0)->member neither with SIGSEGV nor with not specified by C lang. error?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.