指针算术是否在联合UB的非活动成员上?

时间:2018-01-10 13:40:06

标签: c++ c++11 language-lawyer pointer-arithmetic

让我们考虑一下这个示例代码:

struct sso
{
    union {
        struct {
            char* ptr;
            char size_r[8];
        } large_str;
        char short_str[16];
    };

    const char* get_tag_ptr() const {
        return short_str+15;
    }
};

[basic.expr]中指定只要结果指向数组的另一个元素(或超过对象的末尾或最后一个元素),就允许指针运算。尽管如此,如果数组是联合的非活动成员,则在本节中未指定会发生什么。我认为这不是问题short_str+15永远不是UB。是不是?

The following question清楚地表明了我的意图

2 个答案:

答案 0 :(得分:5)

编写return short_str+15;,您获取其生命周期可能尚未开始的对象的地址,但这不会导致未定义的行为,除非您取消引用它。

  

[basic.life]/1.2

     

如果对象是联盟成员或其子对象,   它的生命周期仅在该联合成员被初始化时才开始   联盟中的成员,或[class.union]中所述。

  

[class.union]/1

     

在联合中,非静态数据成员是活动的   name是指生命周期已经开始但尚未结束的对象   ([basic.life])。最多一个对象的非静态数据成员   union类型可以随时激活,即最多值   其中一个非静态数据成员可以存储在任何联合中   时间。

  

[basic.life]/6

     

在一个对象的生命周期开始之前但是在存储之后的那个   对象将占用已经分配或者,在一生之后   对象已经结束,并且在对象占用的存储之前   重用或释放,任何表示地址的指针   可以使用对象将位于或位于的存储位置   但只是在有限的方面。对于正在建造的物体或   破坏,见[class.cdtor]。否则,这样的指针指的是已分配   存储([basic.stc.dynamic.allocation]),并使用指针,就像指针一样   类型void *,定义明确。通过这样的指针间接是   允许但最终的左值只能以有限的方式使用,   如下所述。
   - [与工会无关的清单]

答案 1 :(得分:2)

联合成员上的指针算法是否会导致混淆取决于指针最终将如何使用。在对标准进行补充以保证“类型访问”规则仅在存在实际别名的情况下应用 或(对于C ++而言)(对于C ++而言)适用的实施方式,指针操作的有效性与它们是在活动成员上还是在非活动成员上执行无关。

考虑一下,例如:

#include <stdint.h>

uint32_t readU(uint32_t *p) { return *p; }
void writeD(double *p, double v) { *p = v; }

union udBlob { double dd[2]; uint32_t ww[4]; } udb;

uint32_t noAliasing(int i, int j)
{
  if (readU(udb.ww+i))
    writeD(udb.dd+j, 1.0);
  return readU(udb.ww+i);
}

uint32_t aliasesUnlessDisjoint(int i, int j)
{
  uint32_t *up = udb.ww+i;
  double *dp = udb.dd+j;

  if (readU(up))
    writeD(dp, 1.0);
  return readU(up);
}

在执行readU期间,将不会通过任何其他方式来访问通过*p访问的存储,因此在执行该函数期间不会出现别名。同样在执行writeD期间。在执行noAliasing的过程中,所有将影响与udb关联的存储的操作都使用全部从udb派生的指针执行,并且显然它们的活动寿命显然不重叠,因此那里没有别名。

在执行aliasesUnlessDisjoint期间,所有访问都使用从udb派生的指针执行,但是在创建up和使用dp之间,通过dp访问存储。 ,并且在创建和使用up之间通过*dp访问存储。因此,除非*upaliasesUnlessDisjoint占据不相交的存储空间,否则udb.ww[i]udb.dd[j]将在执行someArray[y]时发生别名。

请注意,即使在上述没有别名的函数中,例如实际没有别名的情况下,gcc和clang都适用类型访问规则。尽管标准明确指出*(someArray+(y))形式的左值表达式等效于[],但如果uint32_t noAliasing2(int i, int j) { if (udb.ww[i]) udb.ww[j] = 1.0; return udb.ww[i]; } uint32_t noAliasing3(int i, int j) { if (*(udb.ww+i)) *(udb.dd+j) = 1.0; return *(udb.ww+i); } ,则gcc和clang仅允许可靠地访问并集内的数组成员使用语法。例如:

noAliasing2

尽管在udb.ww[i]上执行操作后,gcc或clang为udb.dd[j]生成的代码将重新加载noAliasing3,但udb.ww[i]的代码将不会重新加载。根据标准,这在技术上是允许的(因为书面规则不允许在任何情况下访问noAliasing!),但这绝不意味着对标准的任何判断作者的一部分认为,gcc和clang的行为适合于高质量的实现。单纯地看标准,我没有发现任何-fstrict-aliasing形式中的任何一种都应比其他形式更有效,但是考虑在public string RenderPartialViewToString(string viewName, object model) { if (string.IsNullOrEmpty(viewName)) viewName = ControllerContext.RouteData.GetRequiredString("action"); ViewData.Model = model; using (StringWriter sw = new StringWriter()) { ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName); ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw); viewResult.View.Render(viewContext, sw); return sw.GetStringBuilder().ToString(); } } 模式下使用gcc或clang的程序员应该认识到gcc和clang对待它们的方式有所不同。