0

我现在用 c99 工作了几个星期,专注于未定义的行为。我想在遵守规则的同时测试一些奇怪的代码。结果是这段代码:

(请原谅我的变量名,我吃了一个小丑)

int main(int arg, char** argv)
{

    unsigned int uiDiffOfVars;
    int LegalPointerCast1, LegalPointerCast2, signedIntToRespectTheRules;
    char StartVar;//Only use to have an adress from where we can move on
    char *TheAccesingPointer;
    int iTargetOfPointeracces;

    iTargetOfPointeracces= 0x55555555;

    TheAccesingPointer = (char *) &StartVar;
    LegalPointerCast2 = (int) &StartVar;
    LegalPointerCast1 = (int) &iTargetOfPointeracces;

    if ((0x80000000 & LegalPointerCast2) != (0x80000000 & LegalPointerCast1))
    {
        //as im not sure in how far 
        //"— Apointer is converted to other than an integer or pointer type (6.5.4)." is treating unsigned integers,
        //im checking this way.
        printf ("try it on next machine!\r\n");
        return 1;
    }


    if ((abs (LegalPointerCast1) > abs (LegalPointerCast2)))
        uiDiffOfVars = abs (LegalPointerCast1) - abs (LegalPointerCast2);
    else
        uiDiffOfVars = abs (LegalPointerCast2) - abs (LegalPointerCast1);

    LegalPointerCast2 = (int) TheAccesingPointer;
    signedIntToRespectTheRules = abs ((int) uiDiffOfVars);

    if ((abs (LegalPointerCast1) > abs (LegalPointerCast2)))
        TheAccesingPointer = (char *)(LegalPointerCast2 + signedIntToRespectTheRules);
    else
        TheAccesingPointer = (char *)(LegalPointerCast2 - signedIntToRespectTheRules);

     printf ("%c\r\n", *TheAccesingPointer);//Will the output be an 'U' ?

    return 0;
}

所以这段代码是最好的未定义行为。我得到不同的结果,无论我没有访问任何我不拥有的内存区域,也没有访问任何未初始化的内存。(AFAIK)

第一个关键规则是,我不允许添加或减去指针,这会让它们离开数组边界。但是我可以将指针转换为整数,我可以根据需要进行计算,不是吗?

我的第二个假设是因为我被允许为指针分配一个有效的地址,将这个计算的地址分配给一个指针是一个有效的操作。由于我使用的是 char 指针,因此也不会违反严格的别名规则,因为 char* 可以对任何东西进行别名。

那么哪条规则被打破了,这会导致 UB?

单个变量也可以理解为“数组”,我违反了这个规则吗?

— 指向或超出数组对象和整数类型的指针的加法或减法会产生不指向或仅超出同一数组对象的结果(6.5.6)。

如果是这样,我也可以这样做吗?

int var;
int *ptr;
ptr = &var;
ptr = ptr + 1;

因为结果几乎可以肯定是未定义的行为。使用 MSVC2010 编译它会输出预期的“U”,但在使用 clang 和 gcc 的 freeBSD 上,我每次都会根据优化级别得到非常有趣和不同的结果。(在我看来,行为定义不应该如此)。

那么有什么想法是导致这种鼻龙的原因吗?

4

2 回答 2

2

您基本上遇到了第 6.3.2.3 段指针广告 5 的转换 from inttochar*在分配 to 中TheAccesingPointer

整数可以转换为任何指针类型。除非前面指定,结果是实现定义的,可能没有正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。

所有abs函数的使用使得它非常依赖于实际的实现会发生什么。基本上它只有在iTargetOfPointeracces地址高于StartVar. 如果你失去了所有出现的abs我认为你会得到'U'大多数(如果不是所有)架构和大多数(如果不是所有)编译器。

具有讽刺意味的是,这不是未定义的行为,而是实现定义的行为。但是,当您没有得到'U'is TheAccesingPointernot 指向引用类型的实体时,很可能它根本没有指向实体。

如果它没有指向一个实体,那么(当然)你将在printf以下段落 6.5.3.2 ad 4中取消引用它时遇到未定义的行为

一元 * 运算符表示间接。如果操作数指向一个函数,则结果是一个函数指示符;如果它指向一个对象,则结果是一个指定该对象的左值。如果操作数的类型为 ''pointer to type'',则结果的类型为 ''type''。如果已为指针分配了无效值,则一元 * 运算符的行为未定义。

让我们详细说明两种情况,堆栈上的所有地址都设置了第 31 位,这在 Linux 下很常见。

场景A:假设&StartVar < &iTargetOfPointeracces那么

  abs(LegalPointerCast1) - abs(LegalPointerCast2)
= LegalPointerCast2 - LegalPointerCast1 (by both < 0)
= (char*)(&StartVar) - (char*)(&iTargetOfPointeracces)
< 0 (by &StartVar < &iTargetOfPointeracces)
So uiDiffOfVars = (char*)(&StartVar) - (char*)(&iTargetOfPointeracces)
and signedIntToRespectTheRules = -uiDiffOfVars (by (int)uiDiffOfVars < 0)
thus  TheAccesingPointer
= (char *)(&StartVar + (char*)(&iTargetOfPointeracces) - (char*)(&StartVar))
= (char*)(&iTargetOfPointeracces)

所以在这种情况下你会得到'U'.

场景B:假设&StartVar > &iTargetOfPointeracces那么

  abs(LegalPointerCast1) - abs(LegalPointerCast2)
= LegalPointerCast2 - LegalPointerCast1 (by both < 0)
= (char*)(&StartVar) - (char*)(&iTargetOfPointeracces)
> 0 (by &StartVar > &iTargetOfPointeracces)
So uiDiffOfVars = (char*)(&StartVar) - (char*)(&iTargetOfPointeracces)
and signedIntToRespectTheRules = uiDiffOfVars (by (int)uiDiffOfVars > 0)
thus TheAccesingPointer
= (char *)(&StartVar + (char*)(&StartVar) - (char*)(&iTargetOfPointeracces))
= (char *)(2*(char*)&StartVar - (char*)(&iTargetOfPointeracces))

在这种情况下,它不太可能TheAccesingPointer指向某个实体,因此在取消引用该指针时会触发未定义的行为。所以我的观点是计算TheAccesingPointer实现定义的,上面的计算很常见。如果计算的指针不指向iTargetOfPointeracces,如场景 B 中那样,则触发未定义的行为。

不同的优化级别可能会导致StartVar' andiTargetOfPointeracces' 在堆栈上的不同顺序,这可以解释不同优化级别的不同结果。

我不认为单个变量算作一个数组。

于 2013-08-17T22:12:59.657 回答
0

一个实现只能定义uintptr_t并且intptr_t如果它可以保证两件事:

  1. 将有效或空指针转换为其中一种类型的行为将产生已定义的行为;

  2. 如果该类型的某个值 q 在数值上等于这种转换的结果,并且如果转换后的指针标识的对象仍然存在,则将类型 q 的值转换回原始指针类型将产生一个比较相等的指针到原来的。

如果 uintptr_t 是 64 位无符号整数类型,代码可以将任何有效指针转换为 uintptr_t 并像任何其他 64 位无符号整数一样对其进行操作,而不考虑原始对象的大小或其他任何内容。另一方面,从标准的角度来看,将这种转换的结果转换回指针类型只会在结果数字与从仍然有效的指针的早期转换结果匹配的情况下产生定义的行为到 uintptr_t。

请注意,顺便说一句,许多实现记录指针和值之间的关系uintptr_t的程度远远超出了标准的要求,但这并不意味着使用这些知识的代码实际上会起作用。例如,给定代码:

static int x,y;
int test(void)
{
  int *p = outsideFunction(&x);
  y=1;
  *p=5;
  return y;
}

一些实现可能会记录程序员可以确定 x 和 y 的相对位移的方法。然而,即使是这样的实现,也可能生成假定对 *p 的写入不可能影响 y 的代码,因为它是一个永远不会占用其地址的静态对象。

于 2017-01-07T21:33:36.407 回答