作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
丹尼尔·穆尼奥斯
验证专家 在工程

Daniel用c++为梦工厂这样的大公司创建了高性能的应用程序. 他还擅长C和ASM (x86)。.

阅读更多

以前在

Meta
分享

今天存在的许多C项目都是几十年前开始的.

UNIX操作系统的开发始于1969年,其代码于1972年用C语言重写. C语言实际上是为了将UNIX内核代码从汇编语言转移到更高级别的语言而创建的, 哪一种可以用更少的代码行完成相同的任务.

Oracle数据库开发始于1977年,其代码于1983年从汇编语言重写为C语言. 它成为了世界上最流行的数据库之一.

1985年的Windows 1.0被释放. 虽然Windows源代码不公开,但据说 它的内核主要是用C语言编写的,还有一些零件正在组装. Linux内核开发始于1991年,它也是用C语言编写的. 第二年, 它在GNU许可下发布,并被用作GNU操作系统的一部分. GNU操作系统本身是使用C和Lisp编程语言开始的, 所以它的许多组件都是用C编写的.

但是C编程并不局限于几十年前开始的项目, 那时还没有像今天这样多的编程语言. 许多 C projects are still started 今天; there are some good reasons 为 that.

世界是如何由C驱动的?

尽管高级语言盛行,但C语言仍在继续赋予世界力量. 下面是一些被数百万人使用的用C语言编写的系统.

微软视窗系统

微软开发了Windows内核 主要用C语言,其中一些部件用汇编语言编写. 几十年来,世界上使用最多的操作系统 大约90%的市场份额,由用C编写的内核提供支持.

Linux

Linux也主要是用C语言编写的,有些部分是汇编的. 世界上最强大的500台超级计算机中约有97%的计算机 运行Linux内核. 它也用于许多个人电脑.

Mac

Mac电脑也使用C语言,因为 OS X内核主要是用C语言编写的. Mac上的每个程序和驱动程序, 比如Windows和Linux电脑, 运行在c驱动的内核上.

移动

iOS, 安卓Windows Phone 内核也是用C编写的. 它们只是现有Mac OS、Linux和Windows内核的移动版改编. 所以你每天使用的智能手机都运行在C内核上.

用C编写的操作系统内核

数据库

世界上最流行的数据库,包括 Oracle数据库,MySQL, MS SQL Server, PostgreSQL,都是用C编写的(前三个实际上都是用C和c++编写的).

数据库用于各种系统:金融, 政府, 媒体, 娱乐, 电信, 健康, 教育, 零售, 社交网络, 网络, 诸如此类.

C语言支持的数据库

3 d电影

3D电影通常是用C和c++编写的应用程序创建的. 这些应用程序需要非常高效和快速, 因为它们处理大量的数据,每秒做很多计算. 效率越高, 艺术家和动画师生成电影镜头所需的时间就越少, 公司节省的钱也就越多.

嵌入式系统

想象一下,有一天你醒来去购物. 叫醒你的闹钟很可能是用C语言编写的. 然后你用微波炉或咖啡机做早餐. 他们也 嵌入式系统 因此很可能是用C语言编写的. 你在吃早餐的时候打开电视或收音机. 它们也是嵌入式系统,由C语言提供支持. 当你用遥控器打开车库门时,你也在使用一个 嵌入式系统,很可能是用C语言编写的.

然后你上了车. 如果它具有以下功能,也可以用C编程:

  • 自动变速器
  • 胎压检测系统
  • 传感器(氧气、温度、油位等.)
  • 记忆座椅和镜子设置.
  • 仪表板显示
  • 防抱死刹车系统
  • 自动稳定控制
  • 巡航控制系统
  • 气候控制
  • 对儿童安全的锁
  • 无钥匙进入
  • 加热座椅
  • 安全气囊控制

你到了商店,把车停好,然后去自动售货机买一杯苏打水. 他们用什么语言编写这个自动售货机的程序? 可能C. 然后你去商店买东西. 收银机也是用C语言编写的. 当你用信用卡付款时? 你猜对了:信用卡读卡器很可能也是用C语言编写的.

所有这些设备都是嵌入式系统. 它们就像小电脑,有一个 单片机在嵌入式设备上运行程序(也称为固件)的微处理器. 该程序必须检测按键并采取相应的行动,并向用户显示信息. 例如, 闹钟必须与用户交互, 检测用户正在按的按钮和, 有时, 它被压了多久, 并相应地对设备进行编程, 同时向用户显示相关信息. 汽车的防抱死制动系统, 例如, 必须能够检测到突然锁定的轮胎,并采取行动,以释放压力刹车片一小段时间, 释放他们, 从而防止失控的打滑. 所有这些计算都是由一个可编程的嵌入式系统完成的.

尽管在嵌入式系统上使用的编程语言可能因品牌而异, 它们最常用C语言编写, 由于语言的灵活性的特点, 效率, 表演。, 和硬件的紧密性.

嵌入式系统通常是用C语言编写的

为什么C语言还在使用?

有很多编程语言, 今天, 这使得开发人员在不同类型的项目中比使用C语言更有效率. 有一些更高级的语言提供了更大的内置库,可以简化JSON的使用, XML, UI, 网页, 客户端请求, 数据库连接, 媒体操纵, 等等......。.

但是尽管如此, 有很多理由相信C编程将在很长一段时间内保持活跃.

在编程语言中,一种方式不可能适合所有人. 以下是C在某些应用程序中是不可战胜的,几乎是强制性的一些原因.

便携性和效率

C几乎是a 便携式组装 语言. 它尽可能接近机器,同时几乎普遍适用于现有的处理器体系结构. 几乎每种现有的体系结构都至少有一个C编译器. 和现在, 因为由现代编译器生成的高度优化的二进制文件, 用手写汇编来提高它们的输出并不是一件容易的事.

这就是它的便携性和效率 其他编程语言的编译器、库和解释器通常是用C实现的。. 解释性语言 Python, Ruby, PHP 它们的主要实现是用C写的吗. 它甚至被其他语言的编译器用来与机器通信. 例如,C是Eiffel和Forth底层的中间语言. 这意味着, 而不是为每个要支持的体系结构生成机器代码, 这些语言的编译器只是生成中间的C代码, C编译器处理机器码的生成.

C也变成了a 通用语 用于开发人员之间的沟通. 作为Alex Allain, Dropbox的工程经理和c编程的创造者.com, 所说的:

C是一种很棒的语言,可以用大多数人都能适应的方式表达编程中的常见想法. 此外,C语言中使用的很多原则,例如, 命令行参数个数argv 命令行参数, 以及循环结构和变量类型-会出现在你学习的很多其他语言中,所以即使他们不懂C,你也能以一种你们都很熟悉的方式与他们交谈.

内存操作

任意内存地址访问和指针算术是一个重要的特性,使C非常适合 系统编程 (操作系统及嵌入式系统).

在硬件/软件边界, 计算机系统和微控制器将它们的外设和I/O引脚映射到内存地址中. 系统应用程序必须对这些自定义内存位置进行读写,以便与外界通信. 因此,C操纵任意内存地址的能力对于系统编程是必不可少的.

可以设计一个微控制器, 例如, 这样内存地址0x40008000中的字节将由 通用异步接收/发送 (或UART, (用于与外设通信的常用硬件组件)每次将地址0x40008001的第4位设置为1, 这是在你设置好那块之后, 外围设备会自动取消设置.

下面是通过UART发送字节的C函数的代码:

#define UART_BYTE *(字符 *)0x40008000 
#define UART_SEND *(挥发性 字符 *)0x40008001 |= 0x08 

send_uart(字符字节) 
{ 
   UART_BYTE = 字节;    // write 字节 to 0x40008000 address 
   UART_SEND;           // set bit number 4 of address 0x40008001 
}

函数的第一行将扩展为:

*(字符 *)0x40008000 = 字节;

这一行告诉编译器解释该值 0x40008000 作为指向a的指针 字符,然后解引用(给出指针指向的值)该指针(最左边) * 运算符),最后赋值 字节 值赋给该解引用指针. 换句话说:写出变量的值 字节 到内存地址 0x40008000.

下一行将扩展为:

*(挥发性 字符 *)0x40008001 |= 0x08;

在这一行中,我们对地址处的值执行了位或操作 0x40008001 和值 0x08 (00001000 在二进制中,I.e.(4位中的1),并将结果保存回地址 0x40008001. 换句话说:我们设置地址为0x40008001的字节的第4位. 我们还声明了地址的值 0x40008001 is 挥发性. 这告诉编译器该值可以被代码外部的进程修改, 所以编译器在写入地址后不会对地址中的值做任何假设. (在这种情况下,在我们通过软件设置之后,UART硬件将取消该位.)这个信息对于编译器的优化器很重要. 如果我们在a中做这个 循环, 例如, 而不指定该值为易失性, 编译器可能会假设这个值在设置后永远不会改变, 并在第一个循环之后跳过执行命令.

资源的确定性使用

系统编程不能依赖的一个常见语言特性是垃圾收集, 对于某些嵌入式系统甚至只是动态分配. 嵌入式应用程序在时间和内存资源方面非常有限. 它们通常用于 实时系统,其中无法提供对垃圾收集器的非确定性调用. 如果由于内存不足而不能使用动态分配, 拥有其他内存管理机制是非常重要的, 比如在自定义地址中放置数据, 在C指针允许的范围内. 严重依赖于动态分配和垃圾收集的语言不适合资源有限的系统.

代码大小

C有一个非常小的运行时. 其代码的内存占用比大多数其他语言都要小.

与c++相比, 例如, 由C生成的二进制文件传输到嵌入式设备的大小大约是由类似的c++代码生成的二进制文件的一半. 造成这种情况的主要原因之一是异常支持.

异常是c++在C之上添加的一个很好的工具,, 如果没有触发并巧妙地实现, 它们几乎没有执行时间开销(但是以增加代码大小为代价)。.

让我们看一个用c++写的例子:

//类A声明. 在其他地方定义的方法; 
A类
{
公众:
   A();                    // Constructor
   ~A();                   // Destructor (called when the object goes out of scope or is deleted)
   void myMethod();        // Just a method
};

//类B声明. 在其他地方定义的方法;
B类
{
公众:
   B();                    // Constructor
   ~B();                   // Destructor
   void myMethod();        // Just a method
};

//类C声明. 在其他地方定义的方法;
C类
{
公众:
   C();                    // Constructor
   ~C();                   // Destructor
   void myMethod();        // Just a method
};

空白myFunction ()
{
   A a;                    // Constructor a.一(). (关卡1)
   {                       
      B b;                 // Constructor b.B()被称为. (2)检查站
      b.myMethod();        //                           (Checkpoint 3)
   }                       // b.~B()析构函数被调用. (4)检查站
   {                       
      C c;                 // Constructor c.C()被称为. (5)检查站
      c.myMethod();        //                           (Checkpoint 6)
   }                       // c.~C()析构函数被调用. (7)检查站
   a.myMethod();           //                           (Checkpoint 8)
}                          // a.A()析构函数被调用. (关卡9)

的方法 A, BC 类在其他地方定义(例如在其他文件中). 因此,编译器无法分析它们,也无法知道它们是否会抛出异常. 因此,它必须准备好处理从它们的任何构造函数抛出的异常, 析构函数, 或者其他方法调用. 析构函数不应该抛出(非常糟糕的做法), 但用户无论如何都可以扔, 或者它们可以通过调用抛出异常的函数或方法(显式或隐式)间接抛出异常.

如果有电话打进来 myFunction 抛出异常 堆栈解除 机制必须能够调用已经构造的对象的所有析构函数. 堆栈展开机制的一种实现将使用 返回地址 该函数的最后一次调用,以验证触发异常的调用的“检查点号”(简单解释如下). 它通过使用一个辅助的自动生成函数(一种查找表)来实现这一点,该函数将用于在该函数体抛出异常时展开堆栈, 这将类似于:

//可能的自动生成函数
void autogeneratedStackUnwindingFor_myFunction(int checkpoint)
{
   开关(关卡)
   {
      //情况1和9:什么都不做;
      案例3:b.~B(); goto destroyA;                     // jumps to location of destroyA label
      案例6:c.~C();                                    // also goes to destroyA as that is the next line
      destroyA: //标签
      案例2:案例4:案例5:案例7:案例8:a.~A();
   }
}

如果从检查点1和9抛出异常,则不需要销毁对象. 对于检查点3, ba 必须销毁. 对于6号检查点, ca 必须销毁. 在任何情况下都必须遵守销毁命令. 对于检查点2、4、5、7和8,只有对象 a 需要被摧毁.

这个辅助函数增加了代码的大小. 这是c++给C增加的部分空间开销. 许多嵌入式应用程序负担不起这个额外的空间. 因此,用于嵌入式系统的c++编译器通常有一个禁用异常的标志. 在c++中禁用异常并不是免费的,因为 标准模板库 严重依赖异常来通知错误. 使用这种修改后的方案,无一例外,需要更多的培训 c++开发人员 检测可能的问题或查找错误.

而且,我们正在谈论的是c++,这种语言的原则是:“你不用为你不用的东西付费。.“对于其他语言来说,二进制大小的增加会变得更糟,因为这些语言会增加其他非常有用但嵌入式系统无法提供的功能的额外开销. 而C语言并没有提供这些额外的功能, 它允许比其他语言更紧凑的代码足迹.

学习C语言的原因

C不是一门难学的语言,所以学习它的所有好处都很便宜. 让我们看看其中的一些好处.

通用语

如前所述,C是a 通用语 为开发人员. 书中或互联网上的许多新算法的实现首先(或唯一)是由它们的作者用C语言提供的. 这为实现提供了最大可能的可移植性. 我曾在网上看到一些程序员因为不了解C的基本概念而费力地将C算法重写为其他编程语言.

请注意,C是一种古老而广泛使用的语言, 所以你可以在网上找到所有用C语言写的算法. 因此,了解这门语言很可能对您大有裨益.

理解机器(用C语言思考)

当我们讨论代码的某些部分的行为时, 或者其他语言的某些特性, 与同事, 我们最终会“用C语言讨论”:这部分是向对象传递一个“指针”还是复制整个对象? 这里会不会有“剧组”? 等等......。.

在分析高级语言的一部分代码的行为时,我们很少讨论(或考虑)一部分代码正在执行的汇编指令. 相反,当讨论机器正在做什么时,我们用C语言清晰地表达(或思考).

此外, 如果你不能停下来思考你正在做的事情, 最终,您可能会对如何(神奇地)完成事情产生某种迷信.

用C语言像机器一样思考

从事许多有趣的C项目

许多 有趣的项目, 从大型数据库服务器或操作系统内核, 小的嵌入式应用程序,你甚至可以在家里做你的个人满意和乐趣, 都是用C语言完成的. 没有理由仅仅因为你不了解老的和小的,就停止做你喜欢的事情, 而是像C这样强大且久经考验的编程语言.

用C语言做很酷的项目

结论

光明会并没有统治世界. C程序员.

C程序设计语言 好像没有截止日期. 它更接近硬件, 良好的可移植性和对资源的确定性使用使其成为操作系统内核和嵌入式软件等低级开发的理想选择. 它的多功能性, 高效率和良好的性能使其成为高复杂性数据处理软件的理想选择, 比如数据库或3D动画. 今天的许多编程语言在其预期用途上都比C语言好,但这并不意味着它们在所有领域都胜过C语言. 当性能是优先考虑的时候,C仍然是无与伦比的.

全世界都在使用c驱动的设备. 不管我们是否意识到,我们每天都在使用这些设备. C语言代表了过去,代表了现在,而且,就我们所能看到的,仍然是软件许多领域的未来.

聘请Toptal这方面的专家.
现在雇佣
丹尼尔·穆尼奥斯

丹尼尔·穆尼奥斯

验证专家 在工程

西雅图,华盛顿州,美国

2013年12月5日成为会员

作者简介

Daniel用c++为梦工厂这样的大公司创建了高性能的应用程序. 他还擅长C和ASM (x86)。.

阅读更多
作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

以前在

Meta

世界级的文章,每周发一次.

<为m aria-label="Sticky subscribe 为m" class="_2ABsLCza P7bQLARO -Ulx1zbi">

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

<为m aria-label="Bottom subscribe 为m" class="_2ABsLCza P7bQLARO -Ulx1zbi">

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.