对已经寻址数组基址的指针应用后递减是否会调用未定义的行为?

在寻找关于以下内容的相关或重复问题后无济于事(我只能用边际正义来描述用C标记的指针算术和后减法问题的绝对数量,但足以说“船载”做了一个坟墓对结果集的不公平)我把它扔在戒指中,希望澄清或转介给我的副本。

如果将后递减运算符应用于如下所示的指针(数组序列的简单反向迭代),则以下代码是否会调用未定义的行为?

#include  #include  int main() { char s[] = "some string"; const char *t = s + strlen(s); while(t-->s) fputc(*t, stdout); fputc('\n', stdout); return 0; } 

最近向我提出6.5.6.p8 Additive运算符,结合6.5.2.p4,Postfix递增和递减运算符,指定甚至在t已经包含s调用的基地址时执行后递减未定义的行为,无论是否评估t的结果值(不是t--表达式结果)。 我只是想知道是否确实如此。

标准的引用部分是:

6.5.6加法运算符

  1. 如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义。

和…几乎紧密耦合的关系……

6.5.2.4后缀增量和减量运算符约束

  1. 后缀增量或减量运算符的操作数应具有primefaces,限定或非限定的实数或指针类型,并且应为可修改的左值。

语义

  1. postfix ++运算符的结果是操作数的值。 作为副作用,操作数对象的值递增(即,将相应类型的值1添加到其中)。 有关约束,类型和转换以及操作对指针的影响的信息,请参阅加法运算符和复合赋值的讨论。 在更新操作数的存储值的副作用之前,对结果的值计算进行排序。 对于不确定顺序的函数调用,后缀++的操作是单个评估。 具有primefaces类型的对象上的Postfix ++是具有memory_order_seq_cst内存顺序语义的读 – 修改 – 写操作.98)

  2. 后缀 – 运算符类似于后缀++运算符,除了操作数的值递减(即,从中减去相应类型的值1)。

前向参考: 加法运算符(6.5.6) ,复合赋值(6.5.16.2)。

在发布的示例中使用post-decrement运算符的原因是为了避免针对数组的基址评估最终无效的地址值。 例如,上面的代码是以下的重构:

 #include  #include  int main() { char s[] = "some string"; size_t len = strlen(s); char *t = s + len - 1; while(t >= s) { fputc(*t, stdout); t = t - 1; } fputc('\n', stdout); } 

忘了这一点,这对于s有一个非零长度的字符串,这个通用算法显然有问题(对某些人来说可能不那么清楚)。 如果s[]代替"" ,则t将被赋予s-1的值,该值本身不是通过其一个过去的地址在s的有效范围内,并且与之后的s进行比较的评估是否好。 如果s具有非零长度,则解决了初始 s-1问题,但只是暂时的,因为最终仍然依赖于该值(无论它是什么)有效用于与s进行比较以终止循环。 这可能会更糟。 它可以天真地:

  size_t len = strlen(s) - 1; char *t = s + len; 

如果s是零长度字符串,则会在其中写入灾难。 打开此问题的重构代码旨在解决所有这些问题。 但…

我的偏执可能会传到我身上,但如果他们真的全力以赴,那就不是偏执狂了。 那么,根据标准(这些部分,或者可能是其他部分),原始代码(如果你现在忘记了它的样子,滚动到这本小说的顶部)确实会调用未定义的行为吗?

我很确定在这种情况下后递减的结果确实是未定义的行为。 后递减明确地从指向对象开头的指针中减去一个,因此结果不指向同一数组的元素,并且通过指针算法的定义(§6.5.6/ 8,如OP)那是未定义的行为。 你从不使用结果指针的事实是无关紧要的。

出了什么问题:

 char *t = s + strlen(s); while (t > s) fputc(*--t, stdout); 

有趣但不相关的事实:标准C ++库中反向迭代器的实现通常在反向迭代器中保存一个指向目标元素之后的指针。 这允许正常使用反向迭代器而不涉及指向容器的“一个开头之前”的指针,如上所述,该指针将是UB。