两次访问相同的内存位置,UB与否?

时间:2015-05-21 13:56:29

标签: c language-lawyer undefined-behavior

this thread中,评分最高的答案获得了很多赞票,甚至获得了赏金。它提出了以下算法:

void RemoveSpaces(char* source)
{
  char* i = source;
  char* j = source;
  while(*j != 0)
  {
    *i = *j++;         // UB?
    if(*i != ' ')
      i++;
  }
  *i = 0;
}

我的下意识反应是这段代码调用了未定义的行为,因为ij指向同一个内存位置,而*i = *j++;等表达式会访问同一个变量两次,用于其他目的,而不是确定要存储的内容,两者之间没有序列点。即使它们是两个不同的变量,它们最初指向相同的存储位置。

但是我不确定,因为我不太清楚相同内存位置的两次无序访问如何在实践中造成任何伤害。

我说的是这是不确定的行为吗?如果是这样,是否有任何关于如何依赖这种UB会导致有害行为的例子?

修改

将此标记为UB的C标准的相关部分是:

C99 6.5

  

在前一个和下一个序列点之间,一个对象应该具有它   通过表达式的评估,最多修改一次存储值。   此外,先前的值应只读以确定该值   存储。

C11 6.5

  

如果标量对象的副作用相对于其中任何一个都没有排序   对同一个标量对象或值有不同的副作用   使用相同标量对象的值进行计算,行为是   未定义。如果有多个允许的排序   表达式的子表达式,如果这样的话,行为是不确定的   任何顺序都会出现无序的副作用。

在两个版本的标准中,文本的实际含义应该相同,但我相信C99文本更容易阅读和理解。

4 个答案:

答案 0 :(得分:6)

有两种情况,两次访问同一个对象没有插入序列点是未定义的行为:

  1. 如果两次修改同一个对象。例如

    int x = (*p = 1, 1) + (*p = 2, 100);
    

    显然,在此之后你不会知道* p是1还是2,但是C标准中的措辞表明它是未定义的行为,即使你写了

    int x = (*p = 1, 1) + (*p = 1, 100);
    

    所以两次存储相同的值并不能保存你。

  2. 如果修改了对象,也可以在不使用read值的情况下读取它来确定对象的新值。这意味着

    *p = *p + 1; 
    
  3. 没问题,因为您阅读了*p,修改了*p,但是您读了*p以确定存储在*中的值。

答案 1 :(得分:3)

这里没有UB(它甚至是惯用的C),因为:

  • *i仅修改一次(*i =
  • j仅修改一次(*j++

当然,在发布的代码中,ij可以指向同一位置(并且在第一次通过时执行)但是......它们仍然是不同的变量。所以在*i = *j++;行:

  • 地址被读入两个指针(ij
  • 读取先前值(* j ++)并用于确定要存储的值
  • 仅修改j指针
  • source通过未修改的指针进行修改

绝对不是UB。

以下调用UB:

*i = *j++ + *j++;  // UB j modified twice
i = i++ + j;       // UB i modified twice

答案 2 :(得分:0)

我认为这不会导致UB。在我看来,这就像说

一样好
<img>

从内存中读取数据然后将其写入相同位置不会造成任何伤害。

答案 3 :(得分:0)

细分表达式*i = *j++。三个运算符的优先顺序为:++(后增量)最高,然后是运算符*(指针取消引用),=最低。

因此,j++将首先进行评估(结果等于j,并且会增加j的效果)。所以表达式相当于

 temp = j++;
 *i = *temp;

其中temp是编译器生成的临时指针。这两个表达式都没有未定义的行为。这意味着原始表达式也没有未定义的行为。