在 C99 的复合文字中使用 postfix/prefix 递增运算符
Using postfix/prefix increment operators in compound literals in C99
有一个例子来自 CARM(C A 参考手册,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 的 postfix/infix 增量返回的值(在复合文字中)。在第一种情况下,它是临时值。后缀递增运算符的结果不是左值。并且前缀增量运算符的结果也不是左值(根据 CARM 第 226 页)。请帮助我理解。 (对不起我的英语)。
我认为这不是关于左值和临时值的问题;而是 H&S 示例中的一个不相关的错误。
在语句p[i]=(int [1]){i++};
中,有一个棘手的问题是i++
之后是否有序列点,这似乎取决于i++
是否是完整表达。在 C17 中明确指出不属于复合文字 的初始值设定项 是完整表达式,这似乎暗示 i++
不是完整表达式并且没有序列点。在那种情况下,所讨论的语句将是未定义的行为,就像您版本中的 p[i]=(int [1]){++i};
一样。
不过,C99好像没有出现“not part of a compound literal”的异常,所以我不太确定是什么情况。但是即使i++
的side effect后面有一个sequence point,据我所知,=
的左右两侧的求值顺序是未指定的。因此,如果编译器选择首先评估右侧(包括其副作用),则语句变为 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]
包含五个不同的指针,都指向 int
s,它们的生命周期在 printf
循环中继续,并且这样 *(p[0]) == 0
、*(p[1]) == 1
等
有一个例子来自 CARM(C A 参考手册,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 的 postfix/infix 增量返回的值(在复合文字中)。在第一种情况下,它是临时值。后缀递增运算符的结果不是左值。并且前缀增量运算符的结果也不是左值(根据 CARM 第 226 页)。请帮助我理解。 (对不起我的英语)。
我认为这不是关于左值和临时值的问题;而是 H&S 示例中的一个不相关的错误。
在语句p[i]=(int [1]){i++};
中,有一个棘手的问题是i++
之后是否有序列点,这似乎取决于i++
是否是完整表达。在 C17 中明确指出不属于复合文字 的初始值设定项 是完整表达式,这似乎暗示 i++
不是完整表达式并且没有序列点。在那种情况下,所讨论的语句将是未定义的行为,就像您版本中的 p[i]=(int [1]){++i};
一样。
不过,C99好像没有出现“not part of a compound literal”的异常,所以我不太确定是什么情况。但是即使i++
的side effect后面有一个sequence point,据我所知,=
的左右两侧的求值顺序是未指定的。因此,如果编译器选择首先评估右侧(包括其副作用),则语句变为 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]
包含五个不同的指针,都指向 int
s,它们的生命周期在 printf
循环中继续,并且这样 *(p[0]) == 0
、*(p[1]) == 1
等