2

Given..:

struct S {
    float f;
    int i;
};

.. can you convert from an int * pointing to an i field to a struct S *?

I am interested in whether there is a spec-conforming way to do this conversion for either/both C and C++. And, if not, I am also in interested in practical implementation specific conversions for clang / gcc / MSVC.

For example, would ..:

    void f(int *i) {
        struct S *s = (struct S *) (((char *) i) - offsetof(S, i));
        printf("%f\n", s->f);
    }

.. be correct?

nmr
  • 16,625
  • 10
  • 53
  • 67
  • 2
    The linux kernel does this type of thing extensively to create 'generic' linked lists. I personally find it distasteful. – Mikel F Oct 06 '22 at 16:30
  • Pointer arithmetic produces a valid pointer if it still points within the same object (or just past it, but can't be dereferenced). If you can add the member offset to the base pointer, you can do the reverse. – Weather Vane Oct 06 '22 at 16:32
  • yeah, style is an orthogonal question, trying to figure out if I can wedge a debugging system into something with minimal blast radius – nmr Oct 06 '22 at 16:33
  • > Pointer arithmetic produces a valid pointer if it still points within the same object (or just past it, but can't be dereferenced). That suggests that you are saying "no"? .. because doing arithmetic on `i *` points out of bounds of the `i` object (though still in bounds of the struct S object.) If so, please post that as an answer. – nmr Oct 06 '22 at 16:34
  • 4
    It is commonly done with `container_of`-like macro. This is extensively used to implement interface inheritance in Linux kernel and other projects – tstanisl Oct 06 '22 at 16:34
  • 1
    @nmr the standard says that every object can be treated as an array of `char`. So yes.. it pointer to `int` but it also point to 4th byte of `struct S`. – tstanisl Oct 06 '22 at 16:36
  • @tstanisl so that means you're saying that my example is spec conforming? If so please post that as an answer – nmr Oct 06 '22 at 16:37
  • 1
    @nmr, I've noticed that the issues about conformance of `container_of` may not come from the macro itself but rather from how the pointer is formed. I've posted a question about it (see https://stackoverflow.com/q/72403103/4989451). The conclusion was that the demonstrated usage is likely conforming. – tstanisl Oct 06 '22 at 16:41
  • Aliasing issues? – lorro Oct 06 '22 at 16:46
  • 1
    This answer is likely very relevant https://stackoverflow.com/a/70417758/4989451 – tstanisl Oct 06 '22 at 18:50
  • So my question is virtually a duplicate of https://stackoverflow.com/questions/25296019/, but I didn't know the insider term "container_of", which was necessary to find that question. Frustrating to deal with these de facto concept names, they're so hard to discover. – nmr Oct 06 '22 at 22:27

1 Answers1

1

.. can you convert from an int * pointing to an i field to a struct S *?

Answer: Yes, but ...

Based on

Pointer

Notes

Although any pointer to object can be cast to pointer to object of a different type, dereferencing a pointer to the type different from the declared type of the object is almost always undefined behavior. See strict aliasing for details.

and

Strict aliasing

Given an object with effective type T1, using an lvalue expression (typically, dereferencing a pointer) of a different type T2 is undefined behavior, unless:

  • T2 is a character type (char, signed char, or unsigned char)

and

offsetof

The macro offsetof expands to an integer constant expression of type size_t, the value of which is the offset, in bytes, from the beginning of an object of specified type to its specified subobject, including padding if any.

it should be alright, but here is the issue:

Pointer arithmetic

For the purpose of pointer arithmetic, a pointer to an object that is not an element of any array is treated as a pointer to the first element of an array of size 1.

The behavior is defined only if both the original pointer and the result pointer are pointing at elements of the same array or one past the end of that array. Note that executing p-1 when p points at the first element of an array is undefined behavior and may fail on some platforms.

  • If the pointer P1 points at an element of an array with index I (or one past the end) and P2 points at an element of the same array with index J (or one past the end), then
    • P1-P2 has the value equal to I-J and the type ptrdiff_t (which is a signed integer type, typically half as large as the size of the largest object that can be declared)

The behavior is defined only if the result fits in ptrdiff_t.

Although, we know (assuming the parameter i of the function really points to a member of struct S), that the pointer is pointing to a valid region in memory (begin of struct S), the compiler might not see it that way. But i am not a language lawyer and since the comments above mentioned the usage in the linux kernel, i will and cannot come to a final assessment.

Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11