当前位置: 首页 > 知识库问答 >
问题:

基于通用char[]的存储和避免严格混叠相关的UB

那宏大
2023-03-14

我正在尝试构建一个类模板,它将一堆类型打包在一个适当大的char数组中,并允许将数据作为单个正确键入的引用访问。现在,根据标准,这可能会导致严格的混淆现象,从而导致未定义的行为,因为我们通过与之不兼容的对象访问char[]数据。具体来说,标准规定:

如果程序尝试通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

  • 对象的动态类型,
  • 对象的动态类型的cv限定版本,
  • 与对象的动态类型类似(如4.4中所定义)的类型,
  • 与对象的动态类型相对应的有符号或无符号类型,
  • 一种类型,它是与对象的动态类型的cv限定版本相对应的有符号或无符号类型,
  • 聚合或并集类型,在其元素或非静态数据成员(递归地包括子聚合或包含并集的元素或非动态数据成员)中包括上述类型之一,
  • 一种类型,它是对象的动态类型的基类类型(可能是cv限定的),
  • char无符号char类型

鉴于突出显示的项目符号的措辞,我想出了以下alias_cast想法:

#include <iostream>
#include <type_traits>

template <typename T>
T alias_cast(void *p) {
    typedef typename std::remove_reference<T>::type BaseType;
    union UT {
        BaseType t;
    };
    return reinterpret_cast<UT*>(p)->t;
}

template <typename T, typename U>
class Data {
    union {
        long align_;
        char data_[sizeof(T) + sizeof(U)];
    };
public:
    Data(T t = T(), U u = U()) { first() = t; second() = u; }
    T& first() { return alias_cast<T&>(data_); }
    U& second() { return alias_cast<U&>(data_ + sizeof(T)); }
};


int main() {
    Data<int, unsigned short> test;
    test.first() = 0xdead;
    test.second() = 0xbeef;
    std::cout << test.first() << ", " << test.second() << "\n";
    return 0;
}

(上面的测试代码,尤其是 Data 类只是这个想法的一个愚蠢的演示,所以请不要指出我应该如何使用 std::p air 或 std:tuplealias_cast模板也应该扩展以处理符合 cv 的条件类型,并且只有在满足对齐要求的情况下才能安全地使用它,但我希望这个片段足以证明这个想法。

这个技巧使 g 的警告静音(当使用 g -std=c 11 -Wall -Wextra -O2 -fstrict-aliasing -Wstrict-aliasing 编译时),并且代码有效,但这真的是告诉编译器跳过基于严格别名的优化的有效方法吗?

如果它无效,那么如何在不违反别名规则的情况下实现这样一个基于字符数组的通用存储类呢?

编辑:用简单的< code>reinterpret_cast替换< code>alias_cast,如下所示:

T& first() { return reinterpret_cast<T&>(*(data_ + 0)); }
U& second() { return reinterpret_cast<U&>(*(data_ + sizeof(T))); }

用g编译时会产生以下警告:

aliastest-so-1.cpp:在T的实例化中

共有1个答案

党建义
2023-03-14

如果你想坚持严格的一致性,使用工会几乎从来都不是一个好主意,他们在阅读活动成员(而且只有这个)时有严格的规则。尽管不得不说,实现喜欢使用联合作为可靠行为的钩子,也许这就是你所追求的。如果是这样的话,我听从迈克·阿克顿(Mike Acton)的意见,他写了一篇关于别名规则的漂亮(而且很长)的文章,他确实评论了通过工会进行投票。

据我所知,这就是您应该如何处理 char 类型的数组作为存储:

// char or unsigned char are both acceptable
alignas(alignof(T)) unsigned char storage[sizeof(T)];
::new (&storage) T;
T* p = static_cast<T*>(static_cast<void*>(&storage));

将其定义为工作的原因是 T 是此处对象的动态类型。当新表达式创建 T 对象时,存储被重用,该操作隐式结束了存储的生存期(由于无符号 char 是一个普通类型,这种情况很容易发生)。

您仍然可以使用例如存储[0]来读取对象的字节,因为这是通过无符号char类型的glvalue读取对象值,这是列出的显式异常之一。另一方面,如果存储是不同但仍然微不足道的元素类型,您仍然可以使上述片段工作,但无法执行存储[0]

最后一个使代码片段变得合理的部分是指针转换。请注意,reinterpret_cast不适合一般情况。考虑到T是标准布局,它可能是有效的(对齐也有额外的限制),但如果是这种情况,那么使用reinterpret_cast将等同于像我一样通过voidstatic_casting。首先直接使用这种形式更有意义,尤其是考虑到在通用上下文中经常使用存储。在任何情况下,转换到和从void是标准转换之一(具有明确定义的含义),您需要static_cast

如果您完全担心指针转换(在我看来这是最薄弱的环节,而不是关于存储重用的争论),那么另一种方法是

T* p = ::new (&storage) T;

如果你想跟踪它的话,就需要在内存中增加一个指针。

我衷心推荐使用std::aligned_storage

 类似资料:
  • 我不知道为什么下面的代码运行得很好,没有< code>gcc错误(< code >-f strict-aliasing-Wstrict-aliasing = 1 )。 如果我遵循严格的别名规则: n1570,§6.5表达式 对象的存储值只能由具有以下类型之一的左值表达式访问: -与对象的有效类型兼容的类型, — 与对象的有效类型兼容的类型的限定版本, -与对象的有效类型对应的有符号或无符号类型的类

  • 当违反严格的混淆现象规则时,我试图掌握未定义的行为。为了理解它,我读了很多关于SO的文章。然而,还有一个问题:我并不真正理解两种类型的非法别名。cpp-参考指出: 类型混淆现象 每当尝试通过AliasedType类型的glvalue读取或修改DynamicType类型对象的存储值时,除非以下之一为真,否则行为未定义: AliasedType和DynamicType类似 AliasedType是Dy

  • 我是android开发的新手,我刚刚从以下链接读到了Romain Guy的“避免android内存泄漏” http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/ 然后我在我的android模拟器上用他著名的代码片段做了一个小测试 此代码应该在更改方向时泄漏第一个活动上下文。因此,我在emulator中运行了

  • 我的自定义UIViewController子类具有存储的闭包属性。闭包签名被定义为采用相同类型的类的单个参数: ...这个想法是,对象将自己作为处理程序的参数传递回去,有点像UIAlertAction初始化器。 此外,为了方便起见,我有一个工厂类方法: ...它执行以下操作: 创建视图控制器的实例, 将完成处理程序分配给属性, 在调用时从顶部/根视图控制器以模态方式呈现它。 我的视图控制器肯定在泄

  • 问题内容: 我曾经在如下所示的接口中一起定义了一组相关的常量(如键): 这为我提供了一种更好的方式将相关常量分组在一起,并通过进行静态导入(而非实现)来使用它们。我知道框架也使用像一样的常量,。 但是,我经常感到,提供代表常数的更好,更强大的方法。 但是使用on 会有性能问题吗? 经过一番研究,我最终陷入了混乱。从这个问题中, 从Android的性能提示中删除了“避免枚举,您只需要整数吗?”很明显

  • 我曾在如下界面中定义一组相关常量,如