热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

数组语法与指针语法和代码生成?

如何解决《数组语法与指针语法和代码生成?》经验,为你挑选了5个好方法。

在Richard Reese所着的"理解和使用C指针"一书中,它在第85页说

int vector[5] = {1, 2, 3, 4, 5};

生成的代码与生成的代码vector[i]不同*(vector+i).该表示法vector[i]生成从位置向量开始的机器代码,从该位置移动 i位置,并使用其内容.符号*(vector+i)生成的机器代码从位置开始vector,添加 i到地址,然后使用该地址的内容.结果相同,生成的机器代码不同.这种差异对大多数程序员来说很少有意义.

你可以在这里看到摘录.这段经文是什么意思?在什么情况下,任何编译器都会为这两个编译器生成不同的 "移动"与基础之间是否存在差异,而"添加"基数是否存在差异?我无法让它在GCC上工作 - 生成不同的机器代码.



1> M.M..:

引用是错的.很遗憾这种垃圾在这十年中仍然存在.事实上,C标准定义x[y]*(x+y).

关于页面后面左值的部分也是完全错误的.

恕我直言,使用本书的最佳方法是将其放入回收箱或刻录.


如果一个联合包含一个数组,gcc将为`theUnion.anArray [i]`和`*(theUnion.anArray + i)`生成不同的机器代码.只有在前一种情况下,gcc才能足够聪明地认识到访问`anArray [i]`可能会影响联盟及其他成员.
我不会说这是_wrong_,但不完整.问题是,_some_编译器可能真的为`x [y]`而不是`*(x + y)`生成不同的机器代码(实际上,同样适用于`*(y + x)`和`y [x]`) .IOW,如果我们在整个引用前加上"On some compilers ...",它实际上是正确的.
日常的方法是使用这本书作为一杯热咖啡的立场.

2> Antti Haapal..:

我有2个C文件: ex1.c

% cat ex1.c
#include 

int main (void) {
    int vector[5] = { 1, 2, 3, 4, 5 };
    printf("%d\n", vector[3]);
}

而且ex2.c,

% cat ex2.c
#include 

int main (void) {
    int vector[5] = { 1, 2, 3, 4, 5 };
    printf("%d\n", *(vector + 3));
}

我将两者编译成汇编,并显示生成的汇编代码的差异

% 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:

QED


C标准非常明确地陈述(C11 n1570 6.5.2.1p2):

    后缀表达式后跟方括号中的表达式[]是数组对象元素的下标名称.下标操作符的定义[]E1[E2]相同(*((E1)+(E2))).由于适用于二元+运算符的转换规则,if E1是一个数组对象(等效地,指向数组对象的初始元素的指针)并且E2是一个整数,因此E1[E2]指定E2-th元素E1(从零开始计数).


此外,as-if规则适用于此 - 如果程序的行为相同,即使语义相同,编译器也可以生成相同的代码.


这假设了特定的编译器和优化,但它通常与我做的一样.我并不满意,因为这样的测试会根据架构的字节代码对基于语言的语言做出假设.
"我感到不满意,因为这样的测试会根据架构的字节代码对基于语言代码的语言做出假设",重点是我们正在讨论生成的程序集,这是一个特定于实现的程序集.关于这种主张,你唯一能做的就是 - 除了注意标准规定的可观察行为的等效性外 - 还要看各种编译器的输出.
有趣的说明:根据引用和实践,"我[矢量]"也有效,尽管在大多数情况下这样做会很糟糕.你不能从位置`i`开始并从这个位置移动`vector`位置.

3> Steve Summit..:

引用的段落是非常错误的.表达式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到地址,然后使用该地址的内容.

说基本相同的事情.

他的语言与旧C FAQ列表的问题6.2中的语言非常类似:

...当编译器看到表达式时a[3],它会发出代码从位置" a"开始,向后移动三个,然后在那里获取字符.当它看到表达式时p[3],它会发出代码从位置" p" 开始,在那里获取指针值,向指针添加三个,最后获取指向的字符.

但当然这里的关键区别在于它a是一个数组而且p是一个指针.常见问题列表不是谈论a[3]*(a+3),而是关于a[3](或*(a+3))在哪里a是一个数组,而不是p[3](或*(p+3))where p是一个指针.(当然这两种情况会产生不同的代码,因为数组和指针是不同的.正如FAQ列表所解释的那样,从指针变量中获取地址与使用数组的地址根本不同.)



4> JimmyB..:

认为原始文本可能指的是一些编译器可能会或可能不会执行的一些优化.

例:

for ( int i = 0; i <5; i++ ) {
  vector[i] = something;
}

for ( int i = 0; i <5; i++ ) {
  *(vector+i) = something;
}

在第一种情况下,优化编译器可以检测到数组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.
}

它甚至可以在可用的情况下使用目标CPU的指针后增量指令.

对于第二种情况,编译器"更难"看到通过某些"任意"指针算术表达式计算的地址显示了在每次迭代中单调推进固定量的相同属性.因此,可能无法((void*)vector+i*sizeof(int))在每次迭代中找到使用额外乘法的优化和计算.在这种情况下,没有(临时)指针被"移动"但只重新计算了一个临时地址.

但是,该声明可能并不普遍适用于所有版本的所有C编译器.

更新:

我检查了上面的例子.似乎没有启用优化,至少gcc-8.1 x86-64为第二个(指针 - 算法)形式生成比第一个(数组索引)更多的代码(2个额外指令).

请参阅:https://godbolt.org/g/7DaPHG

然而,任何优化接通(-O... -O3)生成的代码是用于两个相同的(长度).



5> supercat..:

标准指定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);
}

GCC为test1生成的代码将假设arr [1] [i]和arr [0] [j]不能别名,但生成的test2代码将允许指针算法访问整个数组.另一方面,gcc将认识到在utest1中,左值表达式uh [i]和uw [j]都访问同一个联合,但它不够复杂,不能注意到*(u.h + i)和*(u.w + j)中的相同utest2.


推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
  • 本文为Codeforces 1294A题目的解析,主要讨论了Collecting Coins整除+不整除问题。文章详细介绍了题目的背景和要求,并给出了解题思路和代码实现。同时提供了在线测评地址和相关参考链接。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 深度学习中的Vision Transformer (ViT)详解
    本文详细介绍了深度学习中的Vision Transformer (ViT)方法。首先介绍了相关工作和ViT的基本原理,包括图像块嵌入、可学习的嵌入、位置嵌入和Transformer编码器等。接着讨论了ViT的张量维度变化、归纳偏置与混合架构、微调及更高分辨率等方面。最后给出了实验结果和相关代码的链接。本文的研究表明,对于CV任务,直接应用纯Transformer架构于图像块序列是可行的,无需依赖于卷积网络。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
author-avatar
源码时代
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有