知道 C 语言会让你成为更好的高级程序员,具体有哪些例子?

15

我知道有一些类似于这个这个问题的存在。让我来解释一下。

在阅读了Joel的文章《回归基础》并看到了许多类似的问题后,我开始想知道,学习像C这样的语言是否真的能使你成为更好的高级程序员,是否有具体的应用实例。

我想要了解的是,是否有很多这样的例子。很多时候,对于这个问题的答案是像“知道C语言可以更好地感觉到底层发生了什么”或者“你需要一个坚实的基础来支撑你的程序”,但这些答案并没有太大意义。我想要理解的是,掌握低层概念将如何在不同的特定方面使您受益,

Joel给出了一些例子:二进制数据库与XML以及字符串。但两个例子并不能充分证明学习C和/或汇编语言的必要性。所以我的问题是:掌握C语言将如何在哪些具体方面使您成为更好的高级程序员?


确实 是 Joel 文章的重点:如果你不知道高级结构实际上在做什么,你可能会愚蠢地使用它们,对吧? - dmckee --- ex-moderator kitten
是的。我所问的,更或少地,是有哪些愚蠢的用法。 - Javier
10个回答

15

我的教学经验和与只学过高级语言的人一起工作的经历告诉我,他们往往在某个高抽象层次上思考,并且他们认为“一切都是免费的”。他们可能成为非常有能力的程序员,但最终他们不得不处理一些性能问题的代码,这时候问题就会出现。

当你使用C语言工作时,你会想到内存分配。你经常考虑内存布局(如果这是一个问题,还包括缓存位置)。你了解为什么某些图形操作代价高昂,如何高效或低效地执行某些套接字行为,以及缓冲区如何工作等。我认为,当你知道它下面的实现方式时,在使用高级语言中的抽象时,有时会给你带来“额外的秘诀”,帮助你思考性能问题。

例如,Java具有垃圾回收器,而您不能直接将东西分配给内存。然而,您可以通过制定某些设计选择(例如,使用自定义数据结构)影响性能,因为出于相同的原因,这也是C语言下的问题。

此外,更普遍地说,我认为对于一位高级编程人员来说,不仅要了解大O符号(大多数学校都会教授),而且在实际应用中常数也很重要(学校往往忽略)。我的经验证明,同时具备这两种语言级别的技能的人倾向于更好地理解常数,也许是因为我上面所描述的原因。

此外,我见过许多高级系统与低级库和基础设施进行接口。例如,某些通信、数据库或图形库。某些特定设备的驱动程序等。如果你是一位高级编程人员,你可能最终必须冒险去那里,了解正在发生的事情至少有所帮助。


谢谢,这正是我所寻找的。 - Javier
同样地,了解汇编语言(以及在汇编中什么是高效的,编译器如何进行优化...例如除法很慢,编译时常量的除法不那么慢,2的幂非常高效)可以使您成为更好的C程序员,特别是如果“更好”包括健康的“更高效”。这在手动矢量化某些内容时特别适用于内部函数,但也适用于一般情况。 - Peter Cordes

9
知道底层技术可以帮助很多事情。要成为一名赛车手,您必须学习和理解轮胎如何抓地的基本物理原理。任何人都可以学会开得很快,但是您需要对“低级别”内容有深入的了解(例如力和摩擦、赛车路线、精细的油门和刹车控制等),才能获得那最后几个百分点的表现,让您在比赛中获胜。 例如,如果您了解计算机中的CPU架构如何工作,您就可以编写更好的代码(例如,如果您知道您的CPU缓存大小或每个CPU缓存行中有一定数量的字节,则可以安排数据结构及其访问方式以充分利用缓存。例如,顺序处理数组的多个元素通常比处理随机元素更快,因为CPU缓存)。如果您拥有多核计算机,则了解线程等低级技术可以带来巨大的好处(就像不理解低级别会导致线程灾难一样)。 如果您了解磁盘I/O和缓存的工作原理,您可以修改文件操作以使其正常工作(例如,如果您从一个文件读取并写入另一个文件,则在RAM中处理大批量数据可以帮助减少代码的读取和写入阶段之间的I/O争用,并大大提高吞吐量)。 如果您了解虚函数的工作原理,您可以设计使用虚函数的高级代码以更好地使用它们。如果使用不当,虚函数可以严重影响性能。 如果您了解绘图的处理方式,您可以使用聪明的技巧来提高绘制速度。例如,您可以通过交替绘制64个白色和黑色的正方形来绘制棋盘。但是一次性绘制32个白色正方形和32个黑色正方形通常更快(因为只需要改变绘制颜色两次而不是64次)。但是,你实际上可以将整个棋盘涂黑,然后在棋盘上和下各画四条白色条纹,这样会更快(只有2次颜色更改,并且只需要绘制9个矩形而不是64个)。这个棋盘技巧教给您一个非常重要的编程技巧:横向思考。通过设计良好的算法,您经常可以对程序的运行情况产生重大影响。

1
+1 不错的棋盘示例。只具备高级语言技能的程序员可能会考虑制作最有效的算法,但有C语言经验的程序员更有可能始终考虑这一点。 - Gavin

5
了解C语言,或者说任何低级编程语言,都可以让你了解内存使用(例如,为什么创建几百万个大型对象是一件坏事)、指针/对象引用的工作原理等等。
问题在于,随着我们创建越来越多的抽象层次,我们发现自己正在进行大量的“乐高积木”编程,而不了解乐高实际上是如何运作的。由于拥有几乎无限的资源,我们开始将内存和资源视为水,往往通过投入更多的铁来解决问题。
虽然不仅限于C语言,但在像Arduino或老式8位处理器这样的更小、内存受限的系统上进行低级别工作具有巨大的好处。它让你以一个更易接近的包装体验到靠近金属的编码,在将应用程序压缩到512K的时间后,你会发现自己将这些技能应用到日常编程中的更大层面。
因此,语言本身并不重要,但对所有位组合方式以及如何在更接近硬件的层次上有效地工作有更深刻的欣赏是对任何软件开发人员有益的一组技能。

2
首先,了解C语言有助于理解操作系统和其他高级语言中的内存工作原理。当你的C#或Java程序占用大量内存时,理解引用(基本上只是指针)也会占用内存,并且了解许多数据结构是如何实现的(这些都可以通过在C中制作自己的数据结构来获得),这有助于你理解字典正在保留大量实际上并未使用的内存。
其次,了解C语言可以帮助你理解如何利用较低级别的操作系统功能。虽然这并不经常需要,但有时你可能需要使用内存映射文件,或者在C#中使用封送处理,而了解C语言将极大地帮助你理解发生这种情况时你正在做什么。
我认为C语言还有助于我理解网络协议,但我无法找到具体的例子。前几天我在阅读另一个SO问题,其中有人抱怨C语言的位域“基本上没什么用”,而我则在想C语言位域如何优雅地表示低级网络协议。高级语言处理位结构总是一团糟!

2
一般来说,你知道的越多,你就会成为更好的程序员。
然而,有时候了解另一种语言,比如C语言,可能会让你做错事情,因为在高级语言(如Python或PHP)中可能存在不正确的假设。例如,有人可能会认为查找列表的长度可能是O(N),其中N是列表的长度。然而,在许多高级语言实例中,这可能并非如此。在Python中,对于大多数类似列表的东西,成本是O(1)。
了解语言的具体细节会有所帮助,但是一般来说知道得更多可能会导致错误的假设。

1
其实大多数“结构良好”的C/C++程序并非如此。您会在计算出长度后将其存储,并参考它而不是每次重新计算。这是一种方便的技巧,我与之合作的许多人都会忘记。他们明确没有C/C++背景。那些有C/C++背景的人倾向于采用“计算一次,重复使用”的方法。其他人似乎认为(我不知道为什么)计算是“免费”的。 - Jason D
C语言程序员是否真的会对特定数据结构的实现进行假设呢?根据我的经验,懂得C语言的程序员更有可能会问“实现方式是什么”,并且更容易理解这对性能特征带来的影响。 - Porculus

1

仅仅“知道”C语言并不能让你变得更好。

但是,如果你理解整个过程,了解本地二进制文件如何工作,CPU如何与其配合,以及体系结构的限制,你可以编写更易于CPU执行的代码。

例如,L1/L2缓存如何影响你的工作,以及应该如何编写代码以在L1/L2缓存中获得更多的命中率。当使用C/C++进行重度优化时,你将不得不深入这种细节。


1

重要的不是了解C语言,而是C语言比许多其他语言更接近裸机。你需要更加清楚如何分配/释放内存,因为你必须自己完成这些工作。自己完成这些工作有助于你理解许多决策的影响。

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

更重要的是,我认为,良好的操作系统、内存架构和算法理解更为重要。如果你了解你的算法如何工作,为什么选择一种算法或数据结构比另一种更好(例如HashSet vs. List),以及你的代码如何映射到计算机上,那么你使用的语言就不重要了。


1

这是我学习和自学编程的经验,特别是理解C语言,回溯到20世纪90年代初,可能有点古老,但热情和动力很重要:

  • 学习理解计算机的低级原理,例如EGA/VGA编程,这里有一个链接到Simtel存档中的C程序员指南PC。
  • 了解TSR的工作原理
  • 下载Bob Stout's snippets的整个存档,这是一个只做一件事情的C代码的大集合 - 学习并理解它们,不仅如此,这些片段的集合力求可移植性。
  • 在线浏览国际混淆C代码比赛(IOCCC),看看C代码如何被滥用并理解语言的细节。最糟糕的代码滥用者是获胜者!下载存档并学习它们。
  • 像我一样,我非常喜欢臭名昭著的Ponzo的C教程,它对我帮助很大,不幸的是,存档很难找到。如果有人知道在哪里获取它们,请留下评论,我将修改答案以包括链接。还有另一个我记得的 - Coronado的[通用?] C教程,但我的记忆模糊了...
  • 查看Dr. Dobb的期刊和C用户期刊这里 - 我不知道你是否仍然可以在印刷品中获取它们,但它们是经典的,我记得手里拿着印刷品的感觉,并急忙回家打入代码看看会发生什么!
  • 获取Turbo C v2的古老版本,我相信你可以从borland.com上获取,并尝试使用16位C编程来获得感觉并玩弄指针...当然它已经古老了,但在其中玩弄指针是可以的。
  • 理解和学习指针,链接这里到遗留Simtel.net - 实现C Guru'ship(想要更好的词)的关键链接,您还将找到与C编程语言相关的大量下载 - 我记得实际上订购了Simtel CD存档并寻找C材料...

2
顺便说一句,我们还可以买一些穿孔卡片,并学习如何手动编程。说真的,学习低级别的东西是有回报的,但学习已经过时且不再存在问题的权宜之计并没有意义。(当然,我也曾经写过TSR程序,在那个年代里。但是我很高兴今天我们拥有了多任务操作系统。) - Niki
@nikie:谢谢。是的,我同意你的看法,但是你不觉得失去了在指尖上拥有绝对控制电脑的手段吗?现在,你必须经过许多繁琐的API调用才能从微软手中夺回对PC的控制权.... ;) - t0mm13b
指针在现代x86-64 C实现中仍然是指针。学习x86分段和“远指针”可能并不明智。 - Peter Cordes
如果你喜欢完全掌控自己的电脑,那么就考虑在用户空间中拥有终极权力以制作出最优快速的代码。现代操作系统仍然可以做到这一点。通过一些指导,现代编译器在许多情况下可以生成接近最优的汇编代码。(了解汇编语言和如何针对当前微架构进行优化是知道引导编译器方向的关键。例如,请参见[为什么这个用C ++编写的代码比我手写的用于测试Collatz猜想的汇编代码更快?](https://dev59.com/Huk6XIcBkEYKwwoYAfK1#40355466)) - Peter Cordes
1
由于这是Ponzo C教程的搜索结果之一,我只想提一下我在这里找到了它(也在这里找到了它)。我能够使用Dosbox(-X)使其正常工作。 - ShreevatsaR

0

在 C 中你必须直接处理的一些事情,而其他语言则会为你抽象掉,包括显式内存管理(malloc)和直接处理指针。

我的女友还差一个学期就能从 MIT 毕业了(他们主要使用 Java、Scheme 和 Python),她目前正在一家以 C++ 为代码库的公司工作。在最初的几天,她很难理解所有的指针/引用等。

另一方面,我发现从 C++ 到 Java 的转换非常容易,因为我从未被传递引用值和传递引用所困惑。

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


-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。方便总是要付出代价的,了解更低层次的东西可以帮助您识别和减轻这些代价。


在C#,C,C++和Java中,for循环保证只执行初始化块一次!也就是第一个分号之前的所有内容。那么你怎么可能每次都得到多个迭代器实例?!?! - Jason D
1
问题不在于for循环,而在于while循环。 - thekindamzkyoulike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接