【问题标题】:What specific examples are there of knowing C making you a better high level programmer?有哪些具体的例子可以让你成为更好的高级程序员?
【发布时间】:2023-03-07 21:14:01
【问题描述】:

我知道this onethis one等问题的存在。让我解释一下。

在阅读了 Joel 的文章 Back to Basics 并看到许多关于 SO 的类似问题后,我开始想知道有哪些具体示例可以说明了解 C 之类的知识可以使您成为更好的高级程序员。

我想知道是否有很多这样的例子。很多时候,这个问题的答案类似于“了解 C 可以让您更好地了解幕后发生的事情”或“您需要为您的程序打下坚实的基础 ",而这些答案没有多大意义。我想了解您将从了解低级概念中受益的不同具体方式,

Joel 举了几个例子:二进制数据库 vs XML 和字符串。但是两个例子并不能真正证明学习 C 和/或汇编的合理性。所以我的问题是:有哪些具体的例子可以让你成为更好的高级程序员?

【问题讨论】:

  • Joel 文章的重点:如果你不知道高级构造实际上在做什么,你可以愚蠢地使用它们,不是吗?
  • 是的。我要问的是,或多或少,你可以以什么方式愚蠢地使用它们。

标签: c low-level high-level


【解决方案1】:

仅仅“了解”C 不会让你变得更好。

但是,如果你了解整个事情,原生二进制文件是如何工作的,CPU 是如何工作的,架构限制是什么,你可能会编写一个更容易用于 CPU 的代码。

例如,L1/L2 缓存如何影响您的工作,以及您应该如何编写代码以在 L1/L2 缓存中获得更多命中。在使用 C/C++ 并进行大量优化时,您将不得不着手处理这类事情。

【讨论】:

    【解决方案2】:

    您必须在 C 中直接处理其他语言从您那里抽象出来的一些事情,包括显式内存管理 (malloc) 和直接处理指针。

    我的女朋友从麻省理工学院(他们主要使用 Java、Scheme 和 Python)毕业一个学期,获得计算机科学学位,她目前在一家代码库使用 C++ 的公司工作。在最初的几天里,她很难理解所有的指针/引用/等。

    另一方面,我发现从 C++ 迁移到 Java 非常容易,因为我从不混淆按值传递引用和按引用传递。

    同样,在 C/C++ 中,更明显的是,原语只是编译器以不同方式处理相同的位集,而不是像 Python 或 Ruby 这样的语言,其中一切都是具有自己独特属性的对象。

    【讨论】:

      【解决方案3】:

      我教学生和与只学习高级语言的人一起工作的经验是,他们倾向于以某种高度抽象的方式思考,并假设“一切都是免费的”。他们可以成为非常称职的程序员,但最终他们必须处理一些有性能问题的代码,然后就会咬他们。

      当您大量使用 C 时,您确实会考虑内存分配。您经常考虑内存布局(如果这是一个问题,还考虑缓存位置)。您了解某些图形操作如何以及为什么会花费很多。某些套接字行为的效率或低效程度。缓冲区是如何工作的,等等。我觉得当你知道它是如何在幕后实现的时,在更高级别的语言中使用抽象有时会给你“额外的秘诀”,当你考虑性能时。

      例如,Java 有一个垃圾收集器,你不能直接将东西直接分配给内存。然而,您可以做出某些影响性能的设计选择(例如,使用自定义数据结构),原因与这在 C 中会成为问题的原因相同。

      此外,更一般地说,我认为对于高级程序员来说,不仅要知道大 O 表示法(大多数学校都教授)很重要,而且在现实生活中的应用程序中,常数也很重要(学校会尝试忽略)。我的轶事经验是,具有两种语言水平的技能的人往往对常数有更好的理解,这可能是因为我上面描述的。

      此外,我见过的许多高级系统与低级库和基础设施接口。例如,一些通信、数据库或图形库。某些设备的一些驱动程序等。如果你是一名高级程序员,你最终可能不得不冒险去那里,这至少有助于了解发生了什么。

      【讨论】:

      • 谢谢,这正是我想要的。
      • 同样,了解汇编语言(以及 asm 中什么是有效的,以及编译器如何优化...例如除法很慢,编译时常数除法不是那么慢,2 的幂非常高效)可以使您成为更好的 C 程序员,特别是如果“更好”包括健康剂量的“更高效”。这在使用内在函数手动矢量化某些东西时非常适用,但通常也是如此。
      【解决方案4】:

      首先,了解 C 有助于您了解内存在操作系统和其他高级语言中的工作原理。当您的 C# 或 Java 程序在内存使用上膨胀时,了解引用(基本上只是指针)也会占用内存,并了解实现了多少数据结构(您从 C 中创建自己的数据结构)有助于您理解您的字典保留了大量实际未使用的内存。

      另一方面,了解 C 可以帮助您了解如何利用较低级别的操作系统功能。您并不经常需要它,但有时您可能需要内存映射文件,或者在 C# 中使用编组,而 C 将极大地帮助您了解当这种情况发生时您在做什么。

      我认为 C 语言也有助于我理解网络协议,但我无法具体说明具体示例。前几天我正在阅读另一个 SO 问题,有人抱怨 C 的位域“基本上没用”,我在想 C 位域如何优雅地表示低级网络协议。处理位结构的高级语言总是一团糟!

      【讨论】:

        【解决方案5】:

        与其说了解 C,不如说 C 比许多其他语言更接近裸机。您需要更加了解如何分配/取消分配内存,因为您必须自己做。自己动手可以帮助您了解您做出的许多决定的影响。

        对我来说,只要您了解编译器/解释器(基本上)如何将您的代码映射到机器上,任何语言都是可以接受的。使用直接公开这一点的语言更容易做到这一点,但是通过阅读,您应该能够弄清楚内存是如何分配和组织的,哪种索引模式比其他索引模式更优化,什么结构是对特定应用等更有效。

        我认为,更重要的是对操作系统、内存架构和算法有很好的理解。如果您了解您的算法是如何工作的,为什么最好选择一种算法或数据结构而不是另一种(例如,HashSet 与 List),以及您的代码如何映射到机器上,那么您使用哪种语言并不重要.

        【讨论】:

          【解决方案6】:

          了解 C 或任何低级编程语言,让您有机会了解内存使用(即为什么创建数百万个重对象是一件坏事)、指针/对象引用如何工作、等等

          问题在于,随着我们创建的抽象级别不断提高,我们发现自己在进行大量“乐高积木”编程,却不了解乐高积木的实际功能。由于拥有几乎无限的资源,我们开始像对待水一样对待记忆和资源,并倾向于通过对情况投入更多的铁来解决问题。

          虽然不限于 C,但使用更小、内存受限的系统(如 Arduino 或老式 8 位处理器)在低级别工作有巨大的好处。它让您以更平易近人的方式体验接近金属编码的体验,并且在花时间将应用程序压缩到 512K 之后,您会发现自己在日常编程中应用了这些技能。

          因此,语言本身并不重要,但更深入地了解所有部分是如何组合在一起的,以及如何在更接近硬件的水平上有效地工作,这对任何软件开发人员都是有益的。

          【讨论】:

            【解决方案7】:

            一般来说,你知道的越多,你就会成为更好的程序员。

            但是,有时了解另一种语言(例如 C)可能会让您做错事,因为在高级语言(例如 Python 或 PHP)中可能存在不正确的假设。例如,可能会假设查找列表的长度可能是 O(N),其中 N 是列表的长度。但是,在许多高级语言实例中可能并非如此。在 Python 中,对于大多数类似列表的东西,成本是 O(1)。

            了解更多关于一种语言的细节会有所帮助,但了解更多一般情况可能会导致人们做出错误的假设。

            【讨论】:

            • 实际上,大多数“结构良好”的 C/C++ 程序并非如此。您将在计算后存储长度并引用它,而不是每次都重新计算它。这是一种方便的技术,我与之共事的许多人都忘记了。他们毫无疑问没有 C/C++ 背景。与我一起工作的具有 C/C++ 背景的人倾向于支持一次计算并重用方法。其他人似乎认为(为什么超出我的范围)计算是“免费的”。
            • 真的有很多C程序员对特定数据结构的实现做出假设的情况吗?根据我的经验,了解 C 的程序员可能会问“这是如何实现的”,并且更有可能理解这对其性能特征意味着什么。
            【解决方案8】:

            了解低级的东西会有很大帮助。

            要成为一名赛车手,您必须学习和理解轮胎抓地力的基本物理原理。任何人都可以学得很快,但是你需要很好地理解“低级”的东西(力和摩擦、赛车线、精细的油门和刹车控制等)才能获得最后百分之几的性能,让你赢得比赛。

            例如,如果您了解 CPU 架构在您的计算机中是如何工作的,那么您可以编写更适合它的代码(例如,如果您知道您有一定的 CPU 缓存大小或每个 CPU 缓存行中有一定数量的字节) ,您可以安排您的数据结构和访问它们的方式以充分利用缓存 - 例如,由于 CPU 缓存,按顺序处理数组的许多元素通常比处理随机元素更快)。如果您有一台多核计算机,那么了解线程工作等低级技术可以带来巨大的好处(就像不了解低级技术会导致线程灾难一样)。

            如果您了解磁盘 I/O 和缓存的工作原理,您可以修改文件操作以使其正常工作(例如,如果您从一个文件读取并写入另一个文件,在 RAM 中处理大批量数据可以帮助减少 I /O 代码的读取和写入阶段之间的争用,并大大提高吞吐量)

            如果您了解虚函数的工作原理,您可以设计使用虚函数的高级代码很好。如果使用不当,它们会严重影响性能。

            如果您了解如何处理绘图,则可以使用巧妙的技巧来提高绘图速度。例如您可以通过交替绘制 64 个白色和黑色方块来绘制棋盘。但是绘制 32 个白色方块然后绘制 32 个黑色方块通常更快(因为您只需更改绘图颜色两次而不是 64 次)。但是您实际上可以将整个电路板绘制为黑色,然后在整个电路板上异或 4 个条纹,然后在电路板上绘制 4 个白色条纹,这还可以更快(2 个颜色变化,只绘制 9 个矩形而不是 64 个)。这个棋盘技巧教你一个非常重要的编程技巧:横向思维。通过精心设计算法,您通常可以对程序的运行情况产生重大影响。

            【讨论】:

            • +1 不错的棋盘示例。只有高级语言技能的程序员可能会考虑制作最有效的算法,但有 C 经验的程序员更可能总是在考虑。
            【解决方案9】:

            一个简单(不完全现实)的例子来说明上面的一些建议。考虑看似无害的

            while(true)
               for(Iterator iter = foo.iterator(); iter.hasNext();)
                   bar.doSomething( iter.next() )
            

            甚至更高的层次

            while(true)
                for(Baz b: foo)
                    bar.doSomething(b)
            

            这里可能存在的问题是,每次 while 循环都会创建一个新对象(迭代器)。如果您只关心程序员的便利性,那么后者肯定更好。但是,如果循环必须高效或机器资源受限,那么您几乎受制于高级语言设计者的摆布。

            例如,对执行高性能 Java 的典型抱怨是在回收垃圾(例如所有已分配的 Iterator 对象)时执行停止。如果您的软件负责跟踪来袭导弹、自动驾驶客机,或者只是不让用户想知道 GUI 停止响应的原因,这不是很好。

            一种可能的解决方案(仍然使用高级语言)是将迭代器的便利性削弱为类似

            Iterator iter = new Iterator();
            while(true)
                for(foo.initAlreadyAllocatedIterator(iter); iter.hasNext();)
                   bar.doSomething(iter.next())
            

            但这只有在您对内存分配有所了解时才有意义......否则它看起来就像一个讨厌的 API。便利总是在某处付出代价,了解较低级别的东西可以帮助您识别和降低这些成本。

            【讨论】:

            • 在 C# 中; C; C++;和 Java 一样,for 循环保证只执行一次初始化块!这就是第一个分号之前的所有内容。那么你怎么能每次都获得迭代器的多个实例呢?!?!
            • 问题不是for循环,而是while循环。
            【解决方案10】:

            这是我学习和自学编程的经验,特别是理解 C,这可以追溯到 1990 年代初期,所以可能有点过时,但热情和动力很重要:

            • 了解计算机的低级原理,例如 EGA/VGA 编程,这里有一个link 到 PC 的 C 程序员指南中的 Simtel 存档。
            • 了解 TSR 的工作原理
            • 下载 Bob Stout's snippets 的整个存档,这是一个大型 C 代码集合,只做一件事 - 研究它们并理解它,不仅如此,sn-ps 集合努力实现可移植性。
            • 在线浏览国际混淆 C 代码竞赛 (IOCCC),了解 C 代码是如何被滥用的,并了解该语言的内在含义。最严重的代码滥用是赢家!下载档案并研究它们。
            • 和我一样,我喜欢臭名昭著的 Ponzo 的 C 教程,它对我帮助很大,不幸的是,很难找到存档。如果有人知道从哪里获得它们,请发表评论,我将修改此答案以包含链接。还有一个我记得的 - Coronado 的 [Generic?] C Tutorial,我对这个的记忆很模糊......
            • 看Dr. Dobb的日记和C用户日记here - 不知道还能不能拿到印刷版,但是很经典,还记得手里拿着印刷版撕下来的感觉回家输入代码看看会发生什么!
            • 获取一个古老的Turbo C v2 副本,我相信您可以从 borland.com 获得它,然后使用 16 位 C 编程来获得感觉并弄乱指针……确保它是古老而古老的,但在玩指针就可以了。
            • 了解和学习指针,将here 链接到旧版Simtel.net - 这是实现 C Guru'ship 的关键链接,因为需要更好的词,您还可以找到大量与 C 编程语言相关的下载 -我记得实际上订购了 Simtel CD Archive 并寻找 C 的东西...

            【讨论】:

            • 在我们做的时候,买一些打孔卡,学习如何手动编程。说真的,学习低级的东西是有回报的,但学习过时的临时解决方案来解决不再存在的问题则不然。 (是的,我以前也写过 TSR。但我很高兴我们今天拥有多任务操作系统。)
            • @nikie:谢谢。是的,我同意你的观点,但你不要错过触手可及的对 PC 的终极权力的手段,现在,你必须通过大量有用的 API 调用来 ummm '收回控制权来自 Microsoft 的 PC....' ;)
            • 指针仍然是现代 C 实现中的指针,在 x86-64 上。学习 x86 分割和“远指针”可能是不明智的。
            • 如果您喜欢在您的 PC 上拥有终极权力,那么请考虑在 用户空间 的终极权力方面,以制作最佳快速代码。您仍然可以在现代操作系统上做到这一点。在许多情况下,现代编译器只需一点点掌握,就可以发出接近最佳的 asm。 (了解 asm 以及如何针对当前微架构进行优化是了解引导编译器的方向的关键。例如,请参阅 Why is this C++ code faster than my hand-written assembly for testing the Collatz conjecture?
            猜你喜欢
            • 2010-09-13
            • 1970-01-01
            • 2016-07-17
            • 1970-01-01
            • 2019-09-29
            • 2014-08-20
            • 2010-10-16
            • 1970-01-01
            • 2010-10-26
            相关资源
            最近更新 更多