14

我知道存在诸如thisthis之类的问题。让我解释。

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

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

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

4

10 回答 10

15

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

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

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

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

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

于 2010-01-16T15:58:28.003 回答
9

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

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

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

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

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

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

于 2010-01-16T16:09:47.027 回答
5

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

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

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

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

于 2010-01-16T16:02:39.783 回答
2

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

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

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

于 2010-01-16T16:05:50.453 回答
2

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

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

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

于 2010-01-16T16:01:03.723 回答
1

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

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

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

于 2010-01-16T16:01:58.260 回答
1

仅仅“知道” C 不会让你变得更好。

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

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

于 2010-01-16T15:51:57.487 回答
1

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

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

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

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

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

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

于 2010-01-16T15:55:06.137 回答
-2

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

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。便利总是在某处付出代价,了解较低级别的东西可以帮助您识别和降低这些成本。

于 2010-01-16T16:29:36.413 回答