zend_portability: Add ZEND_CONTAINER_OF()#21903
Conversation
|
I think this relies on UB (pointer arithmetic on NULL) |
That branch is never evaluated; it's also the “accepted” solution to define |
This doesn't matter for UB.
The Linux kernel is compiled with various flags that makes various UB defined, so I'm not so sure this is a valid comparison. |
|
I believe it's not UB unless executed, but we can probably achieve something similar with static assets? |
The issue is that compilers can assume UB never happens, and then start removing stuff or doing whatever they want if that code is present (see famous blog article about UB: https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633) |
|
So, I have a different understanding.
Yes, but this branch is already provably dead. UB allows the compiler to make some assumptions. For example:
In other words, my understanding is: UB in dead branches has an effect on the result of the program, i.e. the compiler can assume the program is correct, and if the branch were reachable the program would not be correct, so the branch is unreachable. But that doesn't mean UB in dead branches makes the behavior of your program undefined if the branches are actually never reached. That's my understanding, though to be honest, the C spec is not nearly explicit enough about this... |
|
Unsure, but now I wonder if we even need this. |
It likely would, but my understanding is that it would be equally UB. You can't just cast between pointers of different types. (and this already affected the original implementation, you may go to My suggestion would be to just go with the established way of writing a |
|
Okay then |
|
Ah. C++. I'll see if I can add a template-variant. |
Changes made with Coccinelle:
@@
type T_container;
identifier member;
expression e;
@@
- (T_container *)(((char *)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
@@
type T_container;
identifier member;
expression e;
typedef uintptr_t;
@@
- (T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
@@
type T_container;
identifier member;
expression e;
@@
- (const T_container *)(((char *)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
@@
type T_container;
identifier member;
expression e;
typedef uintptr_t;
@@
- (const T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)
A follow-up change to the new `ZEND_CONTAINER_OF()` macro will preserve the `const`-ness of the input pointer. By wrapping this macro inside a macro rather an an inline function we can preserve the `const`-ness across all layers.
| return reinterpret_cast<T*>(reinterpret_cast<char*>(ptr) - offset); | ||
| } | ||
|
|
||
| # define ZEND_CONTAINER_OF(ptr, Type, member) zend_container_of<Type, decltype(Type::member)>(ptr, offsetof(Type, member)) |
There was a problem hiding this comment.
I was about to say something about the decltype but in another hand it can catch ptr/member mismatches so ...
There was a problem hiding this comment.
speaking of this, once C++ 20 is recognised widely as minimum, C++20 concepts can help reinforce types contracts here ... we ll see :)
There was a problem hiding this comment.
I was about to say something about the decltype but in another hand it can catch ptr/member mismatches so ...
Yes, that was the goal. I tested the macro in a standalone C++ file to verify that it handles both const and other types correctly - just like the C23 version does.
If there already is a way to improve the definition right now (e.g. by avoiding two overloads to handle const/non-const), I'm happy to take suggestions. I don't regularly do C++, so while I'm somewhat familiar with what it can offer in theory, I am not comfortable applying that.
There was a problem hiding this comment.
it s way easier to do such things with C++20 but somewhat doable with older standards. let me think for a moment.
There was a problem hiding this comment.
it seems doable https://godbolt.org/z/no6MhGase std::conditional_t might have been nice to have but it s a c++14 thing...
There was a problem hiding this comment.
I believe the minimum we support is C++17.
Unfortunately your suggestion is taking const-ness from M, not from ptr. Thus https://godbolt.org/z/hh37h6ene doesn't compile.
Given that my current solution works, it's probably not worth spending further time on this.
Changes made with Coccinelle: