-1

The simiar code also appeared here: list_entry in Linux

But my question is that why cast it to unsigned long? Because according to C99 6.3.2.3, the behavior is undefined or implementation defined to cast a pointer to interger

6.3.2.3: 6. Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

So why previous kernel cast a pointer to unsigned long ? Is it a bug?

The code:

#include <stdio.h>

struct foobar{
    unsigned int foo;
}tmp;
printf("\taddress and offset of tmp->foo= %p\n",
       (struct foobar *)(((char *)&tmp.foo)
               - ((unsigned long)&((struct foobar *)0)->foo)));     

I use gcc and a x86_64 machine , and I know the code from this tutorial

Ryan Chen
  • 150
  • 12
  • 2
    Are you sure that compiles? I can't parse the final `0`. In fact, that makes little sense. It uses both an instance (`tmp`) but also the `NULL`-trick. Why?!11 – unwind Apr 05 '18 at 14:50
  • 2
    Yeah.. why?.... – Eugene Sh. Apr 05 '18 at 14:52
  • 1
    This is really bad... Seems like you need `container_of` macro. – Jazzwave06 Apr 05 '18 at 14:52
  • 1
    The final `0` is a syntax error, the rest is a very aweful way to calculate the address of `tmp`. – mch Apr 05 '18 at 14:53
  • `&((struct foobar *)0)->foo` is this an attempt to create a null pointer of type `(struct foobar *)` and then dereference it? – giusti Apr 05 '18 at 14:55
  • Please also note that the format specifier `%p` requires a `void*`. – Bob__ Apr 05 '18 at 14:55
  • Here is a great explanation : https://stackoverflow.com/questions/394767/pointer-arithmetic – Thomas Blanquet Apr 05 '18 at 14:55
  • @giusti it's a pointer to adress 0 – Martin Chekurov Apr 05 '18 at 14:56
  • 1
    Well, now I know one place to *never* trust a degree from. Casting a pointer to `unsigned long`??!?!?! Ouch. Looks like an example of "Those that can, do. Those that can't, teach". – Andrew Henle Apr 05 '18 at 14:59
  • 2
    @AndrewHenle Note the subdomain name :) – Eugene Sh. Apr 05 '18 at 15:00
  • @EugeneSh. Oh Lord. :-/ – Andrew Henle Apr 05 '18 at 15:02
  • Shouldn't this throw a segmentation fault? Because he is dereferencing a NULL pointer? – Kami Kaze Apr 05 '18 at 15:02
  • @KamiKaze Not dereferencing, just taking an address. – Eugene Sh. Apr 05 '18 at 15:03
  • @Kami Kaze it's not a null pointer it's a pointer to adress 0... – Martin Chekurov Apr 05 '18 at 15:03
  • @MartinChekurov What's the difference? – giusti Apr 05 '18 at 15:07
  • 2
    It's not an example of great code, but as long as it's only to teach how structures are laid out in memory for illustrative purposes, it's okay. Portable code shoud use `offsetof` instead. Some compiler standard libraries actually use this trick to implement `offsetof` although they'd probably use `size_t` instead of `unsigned long`. – Ian Abbott Apr 05 '18 at 15:10
  • size_t is unsigned integer type, which is 32 bit, and not okay for 64 bit pointer, although in this case, there will be no structure offset which is bigger than 2^32 – Ryan Chen Apr 05 '18 at 15:19
  • @RyanChen `unsigned long` can be 32-bit integer type too. – Weather Vane Apr 05 '18 at 15:20
  • 3
    The only unsigned integer type which is guaranteed to hold pointer is `uintptr_t` – Eugene Sh. Apr 05 '18 at 15:21
  • @WeatherVane is there any machine which the `pointer` is 64 bit and `long` is 32 bit? – Ryan Chen Apr 05 '18 at 15:24
  • 1
    That's not the point. An `unsigned long` is at least 32 bits, and the compiler writer can choose to do that. – Weather Vane Apr 05 '18 at 15:29
  • @WeatherVane you are right, I will use `uintptr_t` from now, thank you – Ryan Chen Apr 05 '18 at 15:33
  • 1
    @RyanChen why not? [The majority of personal computers nowadays](https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models) have [64-bit pointer and 32-bit long](https://stackoverflow.com/q/384502/995714) although most 64-bit OSes have 64-bit `long` – phuclv Apr 05 '18 at 15:40
  • Possible duplicate of [list\_entry in Linux](https://stackoverflow.com/questions/5550404/list-entry-in-linux) – anatolyg Apr 05 '18 at 16:02
  • @anatolyg No, my question is different. I emphasize on the cast. Already edit it to make it more clear – Ryan Chen Apr 05 '18 at 16:30
  • 1
    @giusti the difference is it's pointing to a valid address - 0(you can dereference and all that), but a null pointer is not valid (you cant dereference etc) – Martin Chekurov Apr 16 '18 at 13:56
  • `unsigned long` will be sufficient unless `struct foobar` is larger than 4GB, and in that case we'd have serious problems with it anyway... (note that we're in kernel code here, which gets to make a lot of other assumptions about pointers, and doesn't need to be strictly conforming). – Toby Speight Nov 22 '21 at 13:38

1 Answers1

0

This code implements container_of macro in the Linux kernel.

The only compiler which officially suitable for Linux kernel is gcc, and it has unsigned long type of the same size as a pointer on all supported platforms. (E.g, for 64-bit platforms gcc follows LP64 types model, which has 64-bit long).

So, conversion from the pointer to unsigned long and back is valid on all those platforms.


Modern container_of implementation in the Linux kernel doesn't use unsigned long for pointers arithmetic, but operates directly with void* - this is another C extension of gcc.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • Why use void* instead of char*? – Ryan Chen Apr 06 '18 at 03:06
  • `Why use void* instead of char*?` - This question is better addressed to Linux kernel developers themselves. I may guess that conversion to `void*` is preferred because it doesn't require explicit cast. – Tsyvarev Apr 06 '18 at 07:12
  • Actually, given that the specific address we're casting points into a struct at address 0, the size of `unsigned long` isn't an issue unless `sizeof (struct foobar)` is larger than `ULONG_MAX` (which is at least 4GB). – Toby Speight Nov 22 '21 at 13:42