代码示例:
struct name
{
int a, b;
};
int main()
{
&(((struct name *)NULL)->b);
}
Run Code Online (Sandbox Code Playgroud)
这是否会导致未定义的行为?我们可以辩论它是否"取消引用无效",但是C11没有定义术语"解除引用".
6.5.3.2/4明确指出*在空指针上使用会导致未定义的行为; 但它并没有说同样的->,也没有定义a -> b为(*a).b; 它为每个运营商分别定义.
->6.5.2.3/4中的语义说:
后缀表达式后跟 - >运算符和标识符指定结构或联合对象的成员.该值是第一个表达式指向的对象的指定成员的值,并且是左值.
但是,NULL并没有指向一个对象,所以第二句似乎没有说明.
相关的可能是6.5.3.2/1:
约束:
一元运算
&符的操作数应该是函数指示符,[]一元或一元运算*符的结果 ,或者是一个左值,它指定一个不是位字段的对象,并且不用寄存器存储类说明符声明.
但是我觉得粗体文本是有缺陷的并且应该读取可能指定对象的左值,按照6.3.2.1/1(左值的定义) - C99弄乱了左值的定义,所以C11必须重写它,也许这个部分错过了.
6.3.2.1/1确实说:
左值是一个表达式(对象类型不是void)可能指定一个对象; 如果左值在评估时未指定对象,则行为未定义
但&操作员确实评估了它的操作数.(它不访问存储的值,但这是不同的).
这种长期推理似乎表明代码会导致UB,但它相当脆弱,我不清楚标准的作者是什么意图.如果事实上他们打算做任何事情,而不是让我们讨论:)
看下面这个简单的代码:
struct Point {
int x;
int y;
};
void something(int *);
int main() {
Point p{1, 2};
something(&p.x);
return p.y;
}
Run Code Online (Sandbox Code Playgroud)
我希望main可以将返回值优化为return 2;,因为something它无法访问p.y,只能返回的指针p.x。
但是,没有一个主要的编译器会优化mainto 的返回值2。上帝保佑。
如果仅允许访问,标准中是否存在可以something修改的内容?如果是,这是否取决于标准布局?p.yp.xPoint
如果我使用something(&p.y);,该return p.x;怎么办?
我正在研究如何在C++中将成员的内存偏移量转换为类,并在维基百科上看到了这一点:
在C++代码中,您不能使用offsetof来访问非Plain Data Data Structures的结构或类的成员.
我尝试了它似乎工作正常.
class Foo
{
private:
int z;
int func() {cout << "this is just filler" << endl; return 0;}
public:
int x;
int y;
Foo* f;
bool returnTrue() { return false; }
};
int main()
{
cout << offsetof(Foo, x) << " " << offsetof(Foo, y) << " " << offsetof(Foo, f);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我收到了一些警告,但它已经编译,运行时它给出了合理的输出:
Laptop:test alex$ ./test
4 8 12
Run Code Online (Sandbox Code Playgroud)
我想我要么误解POD数据结构是什么,要么我错过了其他一些难题.我不知道问题是什么.
在ANSI C中,offsetof定义如下.
#define offsetof(st, m) \
((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))
Run Code Online (Sandbox Code Playgroud)
为什么这不会引发分段错误,因为我们正在取消引用NULL指针?或者这是某种编译器黑客,它看到只有偏移的地址被取出,所以它静态地计算地址而不实际解除引用它?这个代码也可以移植吗?
该问题是针对以下问题的后续措施:当它实际上未指向char数组时,是否将其添加到“ char *”指针UB?
在CWG 1314中,CWG确认使用指针在标准布局对象中执行指针算术是合法的unsigned char。这似乎意味着与链接的问题类似的某些代码应按预期工作:
struct Foo {
float x, y, z;
};
Foo f;
unsigned char *p = reinterpret_cast<unsigned char*>(&f) + offsetof(Foo, z); // (*)
*reinterpret_cast<float*>(p) = 42.0f;
Run Code Online (Sandbox Code Playgroud)
(为了更清楚起见,我用替换char了unsigned char。)
但是,似乎C ++ 17中的新更改意味着该代码现在是UB,除非std::launder在两个reinterpret_casts 之后都使用。reinterpret_cast两种指针类型之间的a结果等于两个static_casts:第一个为cv void*,第二个为目标指针类型。但是[expr.static.cast] / 13暗示这会生成指向原始对象的指针,而不是指向目标类型的对象的Foo指针,因为类型的对象不能与unsigned char对象的第一个字节进行指针互换,也不是一个unsigned char对象在的第一个字节f.z指针相互转换与f.z本身。
我很难相信委员会打算进行更改以打破这种非常普遍的习惯用法,从而使C ++ 17之前的所有用法都不offsetof确定。
来自MSVC实施的示例:
#define offsetof(s,m) \
(size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m))
// ^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
可以看出,它取消引用空指针,通常会调用未定义的行为.这是规则的例外还是正在发生的事情?
C++ 17标准说:
有条件地支持
offsetof使用非标准布局类的宏.
有条件地支持
程序构造不需要实现支持
我觉得这个定义offsetof不太精确.
这是否意味着我可以安全地尝试在非标准布局类中使用它?
"条件支持"与实现定义有何不同?
编译器是否不支持offsetof生成诊断所需的特定类型的类?
我需要使用offsetof从template一个成员选择.如果您原谅尴尬的语法,我想出办法:
template <typename T,
typename R,
R T::*M
>
constexpr std::size_t offset_of()
{
return reinterpret_cast<std::size_t>(&(((T*)0)->*M));
};
Run Code Online (Sandbox Code Playgroud)
用法并不完美(最好烦恼):
struct S
{
int x;
int y;
};
static_assert(offset_of<S, int, &S::x>() == 0, "");
static_assert(offset_of<S, int, &S::y>() == sizeof(int), "");
Run Code Online (Sandbox Code Playgroud)
非constexpr形式更容易使用:
template <typename T, typename R>
std::size_t offset_of(R T::*M)
{
return reinterpret_cast<std::size_t>(&(((T*)0)->*M));
};
Run Code Online (Sandbox Code Playgroud)
明显的缺点是它不是在编译时完成的(但更容易使用):
int main()
{
std::cout << offset_of(&S::x) << std::endl;
std::cout << offset_of(&S::y) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
我正在寻找的是语法像非constexpr品种,但仍然完全编译时间; 但是,我无法想出它的语法.我也很满意offset_of<&S::x>::value(就像其他类型特征一样),但无法弄清楚它的语法魔法.
我有以下代码:
#include <stddef.h>
int main() {
struct X {
int a;
int b;
} x = {0, 0};
void *ptr = (char*)&x + offsetof(struct X, b);
*(int*)ptr = 42;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
最后一行执行对 的间接访问x.b。
该代码是根据任何 C 标准定义的吗?
我知道:
*(char*)ptr = 42;已定义,但仅定义了实现。ptr == (void*)&x.b我猜想访问ptrvia指向的数据int*不会违反严格的别名规则,但我不完全确定该标准保证了这一点。
是否有可能有一个成员变量,它能够从指向自身的指针计算指向包含对象的指针(在它的方法中)?
让我们在API中包含一个外部调用接口,如下所示:
template <typename Class, MethodId Id, typename Signature>
class MethodProxy;
template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodProxy<Class, Id, ReturnT ()(Arg1T) {
public:
ReturnT operator()(Class &invocant, Arg1T arg1);
};
Run Code Online (Sandbox Code Playgroud)
类似地,对于从0到N的其他数量的参数.对于外来的每个类,一个C++类声明具有一些特征,并且该模板使用这些特征(以及参数类型的更多特征)来查找和调用外部方法.这可以像:
Foo foo;
MethodProxy<Foo, barId, void ()(int)> bar;
bar(foo, 5);
Run Code Online (Sandbox Code Playgroud)
现在我想做的是以Foo这种方式定义,我可以这样称呼:
Foo foo;
foo.bar(5);
Run Code Online (Sandbox Code Playgroud)
不重复签名多次.(显然创建一个静态成员并在方法中包装调用很简单,对吧).嗯,事实上,这仍然很容易:
template <typename Class, MethodId Id, typename Signature>
class MethodMember;
template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodMember<Class, Id, ReturnT ()(Arg1T) {
MethodProxy<Class, Id, Signature> method;
Class …Run Code Online (Sandbox Code Playgroud)