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

Who*_*aig 7 c arrays pointers

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

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

#include <stdio.h>
#include <string.h>

int main()
{
    char s[] = "some string";
    const char *t = s + strlen(s);

    while(t-->s)
        fputc(*t, stdout);
    fputc('\n', stdout);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

标准的引用部分是:

6.5.6加法运算符

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

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

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

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

语义

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

  2. 所述后缀-操作者是类似于后缀++操作,不同的是操作数的值被递减(即,相应的类型的值1被从中减去).

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

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

#include <stdio.h>
#include <string.h>

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);
}
Run Code Online (Sandbox Code Playgroud)

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

    size_t len = strlen(s) - 1;
    char *t = s + len;
Run Code Online (Sandbox Code Playgroud)

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

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

ric*_*ici 7

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

有什么不对:

char *t = s + strlen(s);
while (t > s) fputc(*--t, stdout);
Run Code Online (Sandbox Code Playgroud)

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