1

有一个从 CARM 借来的示例(CA Reference Manual, Samuel P. Harbison III, Guy L. Steele Jr., 2002, Prentice Hall),第 218-219 页。我在一个源中编写了所有三个代码块:

#include <stdio.h>

void f1(){
    int *p[5];
    int i=0;
    m:
    p[i]=(int [1]){i};
    if(++i<5)goto m;
    printf("f1: ");
    for(i=0;i<5;++i)
        printf("p[%d]=%d ",i,*(p[i]));
    printf("\n");
    fflush(stdout);
}

void f2(){
    int *p[5];
    int i=0;
    p[i]=(int [1]){i++};
    p[i]=(int [1]){i++};
    p[i]=(int [1]){i++};
    p[i]=(int [1]){i++};
    p[i]=(int [1]){i++};
    printf("f2: ");
    for(i=0;i<5;++i)
        printf("p[%d]=%d ",i,*(p[i]));
    printf("\n");
    fflush(stdout);
}

void f3(){
    int *p[5];
    int i;
    for(i=0;i<5;i++){
        p[i]=(int [1]){i};
    }
    printf("f3: ");
    for(i=0;i<5;++i)
        printf("p[%d]=%d ",i,*(p[i]));
    printf("\n");
    fflush(stdout);
}

int main(){ f1(); f2(); f3(); }

f2 功能无法正常工作:

user@debian:~/test7$ gcc -std=c11 ./carm_1.c -o carm_1
user@debian:~/test7$ ./carm_1
f1: p[0]=4 p[1]=4 p[2]=4 p[3]=4 p[4]=4 
f2: p[0]=-2106668384 p[1]=-2106668408 p[2]=32765 p[3]=2 p[4]=3 
f3: p[0]=4 p[1]=4 p[2]=4 p[3]=4 p[4]=4

但是当我重写它时:

#include <stdio.h>

void f1(){
    int *p[5];
    int i=0;
    m:
    p[i]=(int [1]){i};
    if(++i<5)goto m;
    printf("f1: ");
    for(i=0;i<5;++i)
        printf("p[%d]=%d ",i,*(p[i]));
    printf("\n");
    fflush(stdout);
}

void f2(){
    int *p[5];
    int i=-1;
    p[i]=(int [1]){++i};
    p[i]=(int [1]){++i};
    p[i]=(int [1]){++i};
    p[i]=(int [1]){++i};
    p[i]=(int [1]){++i};
    printf("f2: ");
    for(i=0;i<5;++i)
        printf("p[%d]=%d ",i,*(p[i]));
    printf("\n");
    fflush(stdout);
}

void f3(){
    int *p[5];
    int i;
    for(i=0;i<5;i++){
        p[i]=(int [1]){i};
    }
    printf("f3: ");
    for(i=0;i<5;++i)
        printf("p[%d]=%d ",i,*(p[i]));
    printf("\n");
    fflush(stdout);
}

int main(){ f1(); f2(); f3(); }

f2 函数工作正常:

user@debian:~/test7$ gcc -std=c11 ./carm_2.c -o carm_2
user@debian:~/test7$ ./carm_2
f1: p[0]=4 p[1]=4 p[2]=4 p[3]=4 p[4]=4 
f2: p[0]=0 p[1]=1 p[2]=2 p[3]=3 p[4]=4 
f3: p[0]=4 p[1]=4 p[2]=4 p[3]=4 p[4]=4

为什么 ?f2 函数的这两个变体的不同之处在于 i 的后缀/中缀增量(在复合文字中)返回的值。在第一种情况下,它是临时值。后缀自增运算符的结果不是左值。并且前缀增量运算符的结果也不是左值(根据 CARM 的第 226 页)。请帮我理解。(对不起我的英语不好)。

4

1 回答 1

1

我不认为这是关于左值和临时值的问题。而是 H&S 示例中的一个不相关的错误。

在语句中p[i]=(int [1]){i++};,有一个棘手的问题是在 之后是否有一个序列点i++,这似乎取决于是否i++是一个完整的表达式。在 C17 中明确指出,不属于复合文字一部分的初始化程序是完整表达式,这似乎暗示i++不是完整表达式并且没有序列点。在这种情况下,有问题的语句将是未定义的行为,就像p[i]=(int [1]){++i};您的版本中的那样。

但是,C99 似乎没有“不是复合文字的一部分”异常,所以我不太确定那里的情况。但即使在 的副作用之后有一个序列点i++,据我所知, 的左右两边的求值顺序仍然=是未指定的。因此,如果编译器选择先评估右侧(包括其副作用),则该语句将变为p[1] = (int [1]){0};并保持p[0]未初始化,从而在取消引用时导致未定义的行为。出于同样的原因,最后一条语句p[5] = (int [1]){4}也变成了 UB,因为p它的长度为 5。

对于始终选择该顺序的编译器,您的代码将起作用;但是对于选择其他顺序的编译器,您的代码可能会变成p[-1] = (int [1]){0}同样是 UB。所以我认为你的版本也不完全正确。

H&S 可能不应该这么聪明,只是写了

int i=0;
p[i] = (int [1]){i};
i++;
p[i] = (int [1]){i};
i++;
p[i] = (int [1]){i};
i++;
p[i] = (int [1]){i};
i++;
p[i] = (int [1]){i};
i++;

那么代码就会是正确的,并且会按照他们所说的去做:p[0], ..., p[4]包含五个不同的指针,所有指针都指向ints ,其生命周期在printf循环中继续,例如*(p[0]) == 0,*(p[1]) == 1等。

于 2021-01-03T19:47:26.140 回答