Eva*_*oll 54 c arrays pointers pointer-arithmetic errata
在Richard Reese所着的"理解和使用C指针"一书中,它在第85页说
Run Code Online (Sandbox Code Playgroud)int vector[5] = {1, 2, 3, 4, 5};生成的代码与生成的代码
vector[i]不同*(vector+i).该表示法vector[i]生成从位置向量开始的机器代码,从该位置移动i位置,并使用其内容.符号*(vector+i)生成的机器代码从位置开始vector,添加i到地址,然后使用该地址的内容.结果相同,生成的机器代码不同.这种差异对大多数程序员来说很少有意义.
你可以在这里看到摘录.这段经文是什么意思?在什么情况下,任何编译器都会为这两个编译器生成不同的 "移动"与基础之间是否存在差异,而"添加"基数是否存在差异?我无法让它在GCC上工作 - 生成不同的机器代码.
M.M*_*M.M 95
引用是错的.很遗憾这种垃圾在这十年中仍然存在.事实上,C标准定义x[y]为*(x+y).
关于页面后面左值的部分也是完全错误的.
恕我直言,使用本书的最佳方法是将其放入回收箱或刻录.
Ant*_*ala 33
我有2个C文件: ex1.c
% cat ex1.c
#include <stdio.h>
int main (void) {
int vector[5] = { 1, 2, 3, 4, 5 };
printf("%d\n", vector[3]);
}
Run Code Online (Sandbox Code Playgroud)
而且ex2.c,
% cat ex2.c
#include <stdio.h>
int main (void) {
int vector[5] = { 1, 2, 3, 4, 5 };
printf("%d\n", *(vector + 3));
}
Run Code Online (Sandbox Code Playgroud)
我将两者编译成汇编,并显示生成的汇编代码的差异
% gcc -S ex1.c; gcc -S ex2.c; diff -u ex1.s ex2.s
--- ex1.s 2018-07-17 08:19:25.425826813 +0300
+++ ex2.s 2018-07-17 08:19:25.441826756 +0300
@@ -1,4 +1,4 @@
- .file "ex1.c"
+ .file "ex2.c"
.text
.section .rodata
.LC0:
Run Code Online (Sandbox Code Playgroud)
QED
C标准非常明确地陈述(C11 n1570 6.5.2.1p2):
- 后缀表达式后跟方括号中的表达式
[]是数组对象元素的下标名称.下标操作符的定义[]是E1[E2]相同(*((E1)+(E2))).由于适用于二元+运算符的转换规则,ifE1是一个数组对象(等效地,指向数组对象的初始元素的指针)并且E2是一个整数,因此E1[E2]指定E2-th元素E1(从零开始计数).
此外,as-if规则适用于此 - 如果程序的行为相同,即使语义不相同,编译器也可以生成相同的代码.
Ste*_*mit 19
引用的段落是非常错误的.表达式vector[i]和*(vector+i)完全相同,可以在所有情况下生成相同的代码.
表达式vector[i]和定义*(vector+i)相同.这是C编程语言的核心和基本属性.任何有能力的C程序员都明白这一点.一本题为" 理解和使用C指针 "的书的作者必须理解这一点.任何C编译器的作者都会理解这一点.这两个片段不会偶然产生相同的代码,但是因为实际上任何C编译器实际上都会立即将一种形式转换为另一种形式,这样当它进入代码生成阶段时,它甚至都不会知道哪种形式最初使用过.(如果C编译器不断生成显著不同的代码,我会很惊讶,而不是.)vector[i]*(vector+i)
事实上,引用的文本与自身相矛盾.正如你所说,这两段经文
该表示法
vector[i]生成从位置开始,从该位置vector移动i位置并使用其内容的机器代码.
和
符号
*(vector+i)生成的机器代码从位置开始vector,添加i到地址,然后使用该地址的内容.
说基本相同的事情.
...当编译器看到表达式时
a[3],它会发出代码从位置"a"开始,向后移动三个,然后在那里获取字符.当它看到表达式时p[3],它会发出代码从位置"p" 开始,在那里获取指针值,向指针添加三个,最后获取指向的字符.
但当然这里的关键区别在于它a是一个数组而且p是一个指针.常见问题列表不是谈论a[3]与*(a+3),而是关于a[3](或*(a+3))在哪里a是一个数组,而不是p[3](或*(p+3))where p是一个指针.(当然这两种情况会产生不同的代码,因为数组和指针是不同的.正如FAQ列表所解释的那样,从指针变量中获取地址与使用数组的地址根本不同.)
我认为原始文本可能指的是一些编译器可能会或可能不会执行的一些优化.
例:
for ( int i = 0; i < 5; i++ ) {
vector[i] = something;
}
Run Code Online (Sandbox Code Playgroud)
与
for ( int i = 0; i < 5; i++ ) {
*(vector+i) = something;
}
Run Code Online (Sandbox Code Playgroud)
在第一种情况下,优化编译器可以检测到数组vector逐个元素地迭代,从而生成类似的东西
void* tempPtr = vector;
for ( int i = 0; i < 5; i++ ) {
*((int*)tempPtr) = something;
tempPtr += sizeof(int); // _move_ the pointer; simple addition of a constant.
}
Run Code Online (Sandbox Code Playgroud)
它甚至可以在可用的情况下使用目标CPU的指针后增量指令.
对于第二种情况,编译器"更难"看到通过某些"任意"指针算术表达式计算的地址显示了在每次迭代中单调推进固定量的相同属性.因此,可能无法((void*)vector+i*sizeof(int))在每次迭代中找到使用额外乘法的优化和计算.在这种情况下,没有(临时)指针被"移动"但只重新计算了一个临时地址.
但是,该声明可能并不普遍适用于所有版本的所有C编译器.
更新:
我检查了上面的例子.似乎没有启用优化,至少gcc-8.1 x86-64为第二个(指针 - 算法)形式生成比第一个(数组索引)更多的代码(2个额外指令).
请参阅:https://godbolt.org/g/7DaPHG
然而,任何优化接通上(-O... -O3)生成的代码是用于两个相同的(长度).
标准指定arr[i]when arr数组对象的行为等同于分解arr为指针,添加i和取消引用结果.尽管这些行为在所有标准定义的案例中都是等效的,但在某些情况下,即使标准确实需要,编译器也会有效地处理行为,并且因此处理arrayLvalue[i]和*(arrayLvalue+i)可能会有所不同.
例如,给定
char arr[5][5];
union { unsigned short h[4]; unsigned int w[2]; } u;
int atest1(int i, int j)
{
if (arr[1][i])
arr[0][j]++;
return arr[1][i];
}
int atest2(int i, int j)
{
if (*(arr[1]+i))
*((arr[0])+j)+=1;
return *(arr[1]+i);
}
int utest1(int i, int j)
{
if (u.h[i])
u.w[j]=1;
return u.h[i];
}
int utest2(int i, int j)
{
if (*(u.h+i))
*(u.w+j)=1;
return *(u.h+i);
}
Run Code Online (Sandbox Code Playgroud)
GCC为test1生成的代码将假设arr [1] [i]和arr [0] [j]不能别名,但生成的test2代码将允许指针算法访问整个数组.另一方面,gcc将认识到在utest1中,左值表达式uh [i]和uw [j]都访问同一个联合,但它不够复杂,不能注意到*(u.h + i)和*(u.w + j)中的相同utest2.