假设有一个结构
struct Thing {
int a;
bool b;
};
假设有一个结构体,我得到了该结构体成员变量 b
的指针,比如作为某个函数的参数:
void some_function (bool * ptr) {
Thing * thing = /* ?? */;
}
如何获取指向包含对象的指针? 最重要的是:在不违反标准规则的情况下,我想要标准定义的行为,而不是未定义或实现定义的行为。
顺便提一下:我知道这种做法绕过了类型安全检查。
假设有一个结构
struct Thing {
int a;
bool b;
};
假设有一个结构体,我得到了该结构体成员变量 b
的指针,比如作为某个函数的参数:
void some_function (bool * ptr) {
Thing * thing = /* ?? */;
}
如何获取指向包含对象的指针? 最重要的是:在不违反标准规则的情况下,我想要标准定义的行为,而不是未定义或实现定义的行为。
顺便提一下:我知道这种做法绕过了类型安全检查。
如果您确定指针确实指向结构体中的成员 b
,就像某人所做的那样
Thing t;
some_function(&t.b);
然后您应该能够使用offsetof
宏来获取指向结构体的指针:
std::size_t offset = offsetof(Thing, b);
Thing* thing = reinterpret_cast<Thing*>(reinterpret_cast<char*>(ptr) - offset);
ptr
实际上没有指向Thing::b
成员,那么如果您使用指针thing
,上述代码将导致未定义的行为。char*
是这种指针算术运算的完全安全和规范方式。 - Karoly Horvathint8_t
甚至无法存在,因为地址和sizeof等等...一切都很好。 - deviantfanint8_t
的实际用途在于它被定义为一个字节,而 char
可能不是。”——不对,你完全颠倒了。在这里应该使用 char
,int8_t
是错误的选择。 - Konrad Rudolphoffsetof
对于非标准布局类型具有未定义的行为(C++11)。实际上,它通常仍然按预期工作。 - davmacoffsetof
实际上是一个C语言的特性,而不是C++语言的特性。C++规范明确表示,在非标准布局的类上使用offsetof
是未定义的。请参见std::is_standard_layout
、offsetof
以及非静态数据成员页面上的标准布局部分。 - Daniel HX* get_ptr(bool* b){
static typename std::aligned_storage<sizeof(X),alignof(X)>::type buffer;
X* p=static_cast<X*>(static_cast<void*>(&buffer));
ptrdiff_t const offset=static_cast<char*>(static_cast<void*>(&p->b))-static_cast<char*>(static_cast<void*>(&buffer));
return static_cast<X*>(static_cast<void*>(static_cast<char*>(static_cast<void*>(b))-offset));
}
buffer
声明为static typename std::aligned_storage<sizeof(X),alignof(X)>::type
吗?此外,通过void*
进行static_cast
而不是直接进行reinterpret_cast
转换为char*
和X*
是否有原因? - Daniel Hreinterpret_cast
,因为映射未经明确定义。static_cast
有一个定义良好的映射。 - Anthony Williamsreinterpret_cast
是定义良好的。我不喜欢这会保留额外的空间,但除此之外似乎还不错。我稍微担心偏移量在这个X
和参数中的那个X
之间不同,但如果编译器这样做了(对于非标准布局,我找不到任何证据表明它不能,但我怀疑任何编译器都不会),我不确定是否有任何方法可以避免它。 - Daniel Hvoid some_function (bool * ptr) {
Thing * thing = (Thing*)(((char*)ptr) - offsetof(Thing,b));
}
我认为没有未定义行为。
#include <iostream>
#include <cstddef>
using namespace std;
struct Thing {
int a;
bool b;
};
template<class T, typename U>
std::ptrdiff_t member_offset(U T::* mem)
{
return
( &reinterpret_cast<const char&>(
reinterpret_cast<const T*>( 1 )->*mem )
- reinterpret_cast<const char*>( 1 ) );
}
template<class T, typename U>
T* get_T_from_data_member_pointer (U * ptr, U T::*pU) {
return reinterpret_cast<T*> (
reinterpret_cast<char*>(ptr)
- member_offset(pU));
}
int main()
{
Thing thing;
thing.b = false;
bool * ptr = &thing.b;
bool Thing::*pb = &Thing::b;
std::cout << "Thing object address accessed from Thing test object lvalue; value is: "
<< &thing << "!\n";
std::cout << "Thing object address derived from pointer to class member; value is: "
<< get_T_from_data_member_pointer(ptr, &Thing::b) << "!\n";
}
1
作为指针是否符合标准,或者可能存在对齐问题或其他问题?我认为除非该值是从指针原始的一组有限操作计算出来的,否则不允许将任何int
转换为指针,尽管可能只是不允许解引用它。虽然在研究这个问题时,我多次想到希望C++默认提供所有类型T
和U
的T* operator-(U*, U T::*)
。 - Daniel Hgcc
提供了这个扩展,因为我非常确定它实际上将指向数据成员变量存储为ptrdiff_t
(https://refspecs.linuxfoundation.org/cxxabi-1.86.html)。 - Daniel Hreinterpret_cast
问题一样,如果我记得我早期学习C++98的时候是正确的。
但是转换很棘手,因为它只是一种推断位移的方法,假设类数据成员指针是用偏移/位移实现的。 它只是为计算指针算术提供基础,没有解引用的内容。
如果我错了,请纠正我。
@Rod的答案指出了那个特定的复杂语法(首先是char引用,然后是取地址)是由于编译器警告而产生的,与您的观察相似。 - rfb
offsetof
可能是未定义的。自 2015 年 11 月以来,你可能已经放弃了这个问题,但如果它仍然相关,并且出现了一个非offsetof
的答案,我建议你实现它。幸运的是,对于一个分配器,你不需要返回一个指针。你可以返回任何满足NullablePointer
和RandomAccessIterator
的指针类结构,并将其typedef
为pointer
。 - Daniel H