C++ 在不同作用域的结构之间强制转换

C++ 在不同作用域的结构之间强制转换,c++,language-lawyer,C++,Language Lawyer,我对在指向可能兼容的结构的指针之间转换感兴趣。他们将使用相同的标记,以相同的顺序使用相同的成员。虽然目标代码库是编译为C或C++,为了简化这个问题,我想把它限制为C++。 在这种情况下,我确信编译器的行为是合理的,但我找不到支持证据证明它必须这样做 激励代码示例如下: #include <cstdio> void foo(void * arg) { struct example { int a; const char * b;

我对在指向可能兼容的结构的指针之间转换感兴趣。他们将使用相同的标记,以相同的顺序使用相同的成员。虽然目标代码库是编译为C或C++,为了简化这个问题,我想把它限制为C++。 在这种情况下,我确信编译器的行为是合理的,但我找不到支持证据证明它必须这样做

激励代码示例如下:

#include <cstdio>

void foo(void * arg)
{
    struct example
    {
        int a;
        const char * b;
    };

    example * myarg = static_cast<example *>(arg);
    printf("meaning of %s is %d\n",myarg->b,myarg->a);
}

void bar(void)
{
    struct example
    {
        int a;
        const char * b;
    };

    example on_stack {42, "life"};
    foo(&on_stack);
}

int main(int,char**)
{
    bar();
}
#包括
void foo(void*arg)
{
结构示例
{
INTA;
常量字符*b;
};
示例*myarg=静态_转换(arg);
printf(“%s的含义是%d\n”,myarg->b,myarg->a);
}
空心条(空心)
{
结构示例
{
INTA;
常量字符*b;
};
_堆栈{42,“生命”}上的示例;
foo(&on_stack);
}
int main(int,char**)
{
bar();
}
我在C++11标准方面运气不太好。关于类的第9节建议示例将是“布局兼容”,这听起来很鼓舞人心,但我找不到“布局兼容”结构后果的描述。特别是,我可以将一个指针转换为另一个指针而不产生任何后果吗

一位同事认为“布局兼容”意味着memcpy将按预期工作。考虑到所讨论的结构也总是可复制的,以下名义上效率低下的代码可能会避免UB:

#include <cstdio>
#include <cstring>

void foo(void * arg)
{
    struct example
    {
        int a;
        const char * b;
    };

    example local;
    std::memcpy(&local, arg, sizeof(example));
    printf("meaning of %s is %d\n", local.b, local.a);
}

// bar and main as before
#包括
#包括
void foo(void*arg)
{
结构示例
{
INTA;
常量字符*b;
};
当地的例子;
std::memcpy(&local,arg,sizeof(示例));
printf(“%s的含义是%d\n”,local.b,local.a);
}
//酒吧和主楼还是和以前一样

这样做的实际动机是当结构定义仅用于少量函数之间的通信时,将其移出全局范围。我理解这是否是一个好主意是有争议的。

是否[basic.lval]10.6允许布局兼容类型之间的别名?否。该节规定:

在其元素或非静态数据成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含的联合的元素或非静态数据成员)

回想一下,“上述类型”是实际类型
T
、动态类型
T
、类似于动态类型的类型、动态类型的某些常量/易失性限定版本或动态类型的有符号/无符号版本

现在,考虑这个代码:

struct T {int i;};
struct U {int i;};

T t;
U *pu = (U*)&t;
pu->i = 5;
现在,让我们从这个角度来看10.6。10.6提出的问题是,glvalue的类型
U
是否包含符合10.1-10.5要求的成员。是吗?请记住,对象
t
的动态类型是
t

  • U
    是否包含类型为
    T
    的成员?没有
  • U
    是否包含一个成员,该成员是
    T
    的常量/易失性限定版本?没有
  • U
    是否包含类似于
    T
    类型的成员?没有
  • U
    是否包含属于
    T
    的签名/未签名版本的成员?没有
  • U
    是否包含一个成员,该成员是
    T
    的有符号/无符号版本的常量/易失性限定版本?没有
由于所有这些都失败了,编译器可以假设修改由
pu
指向的对象不会修改对象
t


供参考:

无论如何,memcopy和指针别名是完全相同的,除了全局结构对齐

不,他们不是。简单复制能力和布局兼容性的规则与别名规则完全不同

琐碎的可复制性是关于复制对象的值表示的合理性,以及这样的副本是否表示合法的对象。布局兼容性的规则是关于
A
的值表示是否与
B
兼容,以便
A
的值可以复制到
B
类型的对象中


别名是指是否可以同时通过
a
的指针/引用和
B
的指针/引用访问对象。严格的别名规则规定,如果编译器看到
a&a
B&B
,则允许编译器假定通过
a
进行的修改不会影响通过
B
引用的对象,反之亦然。[basic.lval]10概述了编译器不允许假设这种情况的情况。

我同意Nicol Bolas的回答,即您不能通过另一种类型访问类型,即使它们与布局兼容。我只想补充一下布局兼容的含义

N3337 9.2/17

如果两种标准布局结构(第9条)类型具有相同数量的非静态 数据成员和相应的非静态数据成员按声明顺序具有布局兼容性 类型(3.9)


现在解释一下这里的所有术语:

(请注意,与布局兼容的类型和与布局兼容的标准布局结构是两种不同的东西)

1。布局兼容类型

布局兼容类型表示相同类型:

N3337 3.9/11:

如果两种类型T1和T2是同一类型,则T1和T2是布局兼容类型

2。标准布局结构:

N3337 9/8

标准布局结构是使用类键结构或类键类定义的标准布局类

(或者换句话说)(因为C++引用结构)

void foo(void * arg)
{
    struct example // only used to declare the layout
    {
        int a;
        const char * b;
    };
    struct r_example {
    int &a;
    const char *&b;
    r_example(void *ext): a(*(static_cast<int*>(ext))),
        b(*(reinterpret_cast<const char **>(
            static_cast<char*>(ext) + offsetof(example, b)))) {}
    };


    r_example myarg(arg);
    printf("in foo meaning of %s is %d\n",myarg.b,myarg.a);
    myarg.a /= 2;
}
void bar(void)
{
    struct example
    {
        int a;
        const char * b;
    };

    example on_stack {42, "life"};
    foo(&on_stack);
    printf("after foo meaning of %s is %d\n",on_stack.b,on_stack.a);
}
in foo meaning of life is 42
after foo meaning of life is 21
    struct p_example {
        int *a;
        const char **b;
    } my_arg;
    my_arg.a = (int *) ext;
    my_arg.b = (const char **)(((char*)ext) + offsetof(example, b));

    printf("in foo meaning of %s is %d\n",*(myarg.b),*(myarg.a));
    *(myarg.a) /= 2;