C ++:在成员变量的指针传递到其他地方的情况下,如何使编译器优化内存访问

问题描述

[edit:这是动机:将变量的指针传递给外部函数可能会意外中断对“相邻”变量的某些优化,因为可能会导致指针指向由原始指针计算出的相邻变量外部功能。以下是原始文章,其中volatile用于模拟当前编译器单元无法访问的外部函数,例如虚拟函数调用关闭的源库函数等]

我想知道以下代码中的return t.a;是否将优化为return 0;

//revision 1
struct T
{
    int a;
    int b;
};

void f_(int * p)
{
    *p = 1;
}
auto volatile f = f_;

int main()
{
    T t;
    t.a = 0;
    t.b = 0;
    for (int i = 0; i < 20; ++i)
    {
        f(&t.b);
    }
    return t.a;
}

it's not。 相当公平,因为函数f中的代码可以使用offsetof获取指向t的指针,然后更改t.a。 因此,优化t.a负载是不安全的。

[edit:再三考虑,offsetof在这里还不够。我们需要container_of,似乎无法在标准C ++中实现。]

但是offsetof不能用于非标准布局类型。所以我尝试了以下代码

//revision 2
#include <type_traits>

struct T
{
private:
    char dummy = 0;
public:
    int a;
    int b;
};
static_assert(!std::is_standard_layout_v<T>);

void f_(int * p)
{
    *p = 1;
}
auto volatile f = f_;

int main()
{
    T t;
    t.a = 0;
    t.b = 0;
    for (int i = 0; i < 20; ++i)
    {
        f(&t.b);
    }
    return t.a;
}

不幸的是,它仍然无法正常工作。

我的问题是:

  • 在上述情况下,优化t.a负载是否安全(修订版2)
  • 如果不是,是否存在某种安排/提议以使其成为可能? (例如,将T设置为更特殊的类型,或将b中的成员T的某些属性说明符)

P.S。以下代码针对return t.a;进行了优化,但是循环产生的代码效率低下。 而且,临时变量变戏法很麻烦。

//revision 3
struct T
{
    int a;
    int b;
};

void f_(int * p)
{
    *p = 1;
}
auto volatile f = f_;

int main()
{
    T t;
    t.a = 0;
    t.b = 0;
    for (int i = 0; i < 20; ++i)
    {
        int b = t.b;
        f(&b);
        t.b = b;
    }
    return t.a;
}

解决方法

使用offsetofT::a到达T::b是非法的,因为没有对象pointer-interconvertibleT::bT::a来自{可以到达。在另一个方向上,possibleT::b到达T::a,因为后者可以与T进行指针互转换。在注释中与Peter相反(尽管在Linux内核中存在container_of宏),&t.b - 1不会产生指向t.a的指针,因为T::b和{{1} }不可指针互换。

请注意,给定指向T::a的指针,您仍然需要使用std::launder来访问T::a

T::b

因此,具有足够侵略性的编译器确实可以得出结论,给定指向auto p = &t.a; std::launder(reinterpret_cast<T*>(p))->b = 1; 的指针,没有替代项f可以访问t.a。但是,目前似乎没有主流编译器执行此优化。