C 是否访问同一内存位置两次?

C 是否访问同一内存位置两次?,c,language-lawyer,undefined-behavior,C,Language Lawyer,Undefined Behavior,在最受欢迎的回答中,获得了大量的支持票,甚至还有一笔赏金。它提出了以下算法: void RemoveSpaces(char* source) { char* i = source; char* j = source; while(*j != 0) { *i = *j++; // UB? if(*i != ' ') i++; } *i = 0; } 我下意识的反应是这个代码调用了未定义的行为,因为i和j指向同一个内存位置,还有一

在最受欢迎的回答中,获得了大量的支持票,甚至还有一笔赏金。它提出了以下算法:

void RemoveSpaces(char* source)
{
  char* i = source;
  char* j = source;
  while(*j != 0)
  {
    *i = *j++;         // UB?
    if(*i != ' ')
      i++;
  }
  *i = 0;
}
我下意识的反应是这个代码调用了未定义的行为,因为
i
j
指向同一个内存位置,还有一个表达式,比如
*i=*j++将访问同一变量两次,用于确定要存储的内容以外的其他目的,其间没有序列点。尽管它们是两个不同的变量,但它们最初指向相同的内存位置

但是我不确定,因为我不太明白对同一内存位置的两个非顺序访问在实践中会造成什么危害

我说这是未定义的行为,对吗?如果是的话,有没有任何例子说明依赖这种UB会导致有害行为


编辑

C标准中标记为UB的相关部分如下:

C996.5

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

C116.5

如果标量对象上的副作用相对于 对同一标量对象或值的不同副作用 使用相同标量对象的值进行计算时,行为为 未定义。如果存在多个允许的订单 表达式的子表达式,如果 未排序的副作用发生在任何订单中


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

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

int k=0;
k=k; //useless but does no harm

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

有两种情况下,在没有中间序列点的情况下访问同一对象两次是未定义的行为:

  • 如果用户修改同一对象两次。比如说

    int x = (*p = 1, 1) + (*p = 2, 100);
    
    显然,在此之后您不知道*p是1还是2,但C标准中的措辞表示,即使您编写了

    int x = (*p = 1, 1) + (*p = 1, 100);
    
    因此,将相同的值存储两次并不能节省您的时间

  • 如果修改了对象,但也读取了对象,而不使用“读取”值来确定对象的新值。这意味着

    *p = *p + 1; 
    

  • 很好,因为您读取了
    *p
    ,所以您修改了
    *p
    ,但您读取了
    *p
    ,以确定存储在
    *
    中的值

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

    因此,将首先评估
    j++
    (结果等于
    j
    ,并增加
    j
    的效果)。所以这个表达式等价于

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

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

    • *i
      仅修改一次(在
      *i=
      中)
    • j
      仅修改一次(在
      *j++
      中)
    当然,在发布的代码
    i
    j
    中,可以指向相同的位置(并在第一次通过时指向),但是。。。它们仍然是不同的变量。所以在
    *i=*j++

    • 地址被读入两个指针(
      i
      j
    • 读取先验值(*j++),并用于确定要存储的值
    • 仅修改
      j
      指针
    • source
      通过未修改的指针进行修改
    它肯定不是UB


    以下内容:

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

    @NatashaDutta不是。仔细阅读C标准中的部分。您可以读取对象以确定新值。如果您出于其他目的读取对象,这是一种未定义的行为。对不起,我现在有点困惑,那么,我可以说,
    *j
    (与
    *I
    相同)是否已经被读取并分配给
    *I
    ,并且在分配发生后,
    *j
    的增量已经排序,这就是定义的行为。我说的对吗?@NatashaDutta注意操作符的优先级,增量是在
    j
    (指针地址)而不是
    *j
    (内容)。@Lundin绝对正确。我的错。我比我自己更喜欢这个答案,虽然我的答案没有错,但这个答案解释得更好,我想我对这个问题的理解不是100%清楚,否则,不管英语不是我的母语,我都会表达得更好。所以你说的是代码是UB,因为(2)?它读取
    j
    ,在同一表达式中,它还将
    j
    增加1,这不是为了确定要存储在
    i
    中的值。先计算并不意味着先执行。我认为编译器更可能将其翻译成机器代码,如
    *I=*j;j++应该不需要临时对象。除非可能涉及的变量是易变的,但这里不是这样。我包括了临时变量,因为这是post increment的语义。正如您所描述的,只要它产生相同的净效果,编译器就可以重新排序并消除临时性。但是它不是必需的。是的,因此不能保证会创建一个临时变量。如果有这样的保证,那么在这种情况下,代码w