微信官方账号,硬核文章第一时间送达!
转自 | CSDN
不久前,发表了一篇文章《C不再是一门编程语言》的观点性文章。作者认为,C它被提升为一个具有威望和权力的角色,它的统治是绝对和永恒的,因此它扭曲了开发者和语言之间的对话。C是编程的通用语言,大家都必须学C,这也导致C不再仅仅是一种编程语言,它已经成为每种通用编程语言都需要遵守的协议。
如今编程语言众多,C语言仍然代表权威。近日,Meta高性能C 数据仓库工程师Daniel这篇文章说,尽管C语言已经推出多年,甚至许多人描述它在暮年,它仍然赋予世界编程权力,并将活跃很长一段时间,因为C语言在某些应用程序中具有压倒性的优势,无与伦比。他列举了C语言是如何渗透到千家万户,影响世界运行的。作者对原文进行了编译,与大家分享!
现在很多C语言项目都是几十年前开始的:
开发于1969年的UNIX1972年,基于C语言重建了操作系统的代码UNIX系统代码从汇编到更高层次的语言,然后用更少的代码完成相同的任务;
开发于1977年的Oracle,其代码也在1983年转向C语言,Oracle是目前最受欢迎的数据库之一;
发布于1985年的Window 1.0操作系统,虽然源代码没有公开,但据说大多数核代码也是基于C语言构建的,有些是汇编的;
开发于1991年的Linux,其核心也是基于C语言的。Linux在1992年基于GNU重新获得许可被视为GNU部分使用操作系统。GNU系统本身也使用C和Lisp它的许多组件都是基于C开发的。
很多人可能会提出,这些系统都是几十年前的项目,当时编程语言不多,选择也少。事实并非如此。C语言不仅仅局限于几十年前的项目,目前很多项目都是基于它启动的。
编程语言层出不穷,各种高级语言如雨后春笋般涌现。然而,C语言仍在赋能世界各地的应用系统。让我们分享一些被数百万人广泛使用的C语言构建系统。
各大流行系统使用的语言
据NetMarketShare统计,Windows操作系统的份额常年徘徊在90%左右,为全球数十亿用户提供服务。其核心代码大多由C语言构建,部分基于汇编。
众所周知,Linux核心主要由C语言编写。在世界500台最强大的超级计算机中,约97%运行Linux它也被用于许多个人电脑。
Mac计算机操作系统也由C语言驱动,因为OS X大部分内核都是用C语言写的。Mac每个程序和驱动程序,就像Windows和Linux和计算机一样,它们都在C语言驱动的核心上运行。
iOS、Android和Windows Phone内核也是用C语言编写的,只是现有的Mac OS、Linux和Windows内核的移动改编。因此,每天使用的智能手机也在C语言上运行。
世界上最受欢迎的数据库包括Oracle、MySQL、MS SQL Server和PostgreSQL,它们都是用C语言建造的(前三个实际上是C和和C )。
金融、政府、媒体、娱乐、电信、乐、电信、健康、教育、零售、社交网络、网络等各种系统。
这些应用程序需要高效和快速。他们需要在几秒钟内计算和处理大量数据,这样艺术家和动画师生成的电影镜头需要的时间越短,公司可以节省更多的资金。这些应用程序大多是基于C和C 制作而成。
想象一下,有一天你醒来:醒来的闹钟可能是用C语言写的,然后你用微波炉或咖啡机做早餐,它们也是嵌入式系统,所以它们可能是基于C语言;当你用遥控器打开车库门时,你可能会用C语言编程。
然后你要开车出去。如果它有以下功能,它也是用C语言编程的。
自动变速器
轮胎压力检测系统
传感器(氧气、温度、油位等。
设置座椅和后视镜的记忆。
仪表盘显示
防抱死制动系统
自动稳定控制
巡航控制
气候控制
儿童安全锁
无钥匙进入
座椅加热
安全气囊控制
去商店,停车,去自动售货机买汽水。那么自动售货机也可能是基于C的。然后你在店里买东西结账,收银机也用C。当你用信用卡付款时?你猜对了:信用卡阅读器也可以用C语言写。
所有这些设备都是嵌入式系统。它们就像小计算机,里面有一个微控制器/微处理器,在嵌入式设备上运行一个程序,也叫固件。在向用户显示信息的同时,程序必须检测按钮并采取相应的行动。例如,闹钟必须与用户互动,测试用户按下什么按钮,有时测试按下多长时间,编程设备,并向用户显示相关信息。例如,汽车的防抱死制动系统必须能够检测到轮胎的突然锁定,并采取行动,在短时间内释放制动压力,消除锁定,防止失控和打滑。所有这些计算都是由编程嵌入式系统完成的。
虽然不同品牌的嵌入式系统使用不同的编程语言,但由于C语言的灵活性、效率、性能和接近硬件的特点,C语言是开发这些项目的首选。
如今,有许多编程语言可以让开发者开发比C更有效的应用程序,这些语言有丰富的内置库,可以简化和JSON、XML、UI、网页、客户端请求、数据库链接、媒体操作等。尽管如此,C为什么还会长期活跃在编程一线?
让我们来看看C语言的无与伦比的优势。
汇编语言的可移植性较差,但C语言是一种非常可移植的语言。它尽可能接近机器,几乎适用于现有的处理器架构。几乎每个现有的架构都至少有一个C语言编译器。如今,由于现代编译器生成了高度优化的二进制文件,用手写汇编来提高输出并不容易。
由于其可移植性和效率高,"其他编程语言的编译器、库和解释器通常用C语言实现"。像Python、Ruby和PHP这些解释性语言的主要实现是基于C语言,它甚至被其他语言的编译器用于与机器通信。例如,C是Eiffel和Forth中间语言。这意味着这些语言的编译器不需要为每个需要支持的架构生成机器代码,而只生成由C编译器处理的中间C代码。
C语言已经成为开发人员之间流的语言。Dropbox工程经理、Cprogramming.com创建者Alex Allain所说:
C语言作为一种伟大的语言,可以让大多数人以可接受的方式表达编程中常见的想法。C其他语言中也会出现语法结构,如命令行参数argc和argv,以及循环结构和变量类型,即使对方不懂C语言,你也能找到一些共同点与他们交谈。
内存管理和指针操作是C语言的重要特征,使C语言成为系统编程(操作系统和嵌入式系统)的最佳合作伙伴。
计算机系统和微控制器外设硬件/软件边界I/O引脚映射到内存地址。为了与外界沟通,系统应用程序必须读写这些自定义的内存位置。C语言操作任何内存地址的能力对系统编程至关重要。
例如,微控制器可以这样设计:每个地址0x40008001的第四位设置为1时,内存地址0x通用异步接收/发送器(或UART,发送与外设通信的常见硬件组件,并在设置后自动取消外设。通过这个演示C函数代码UART发送一个字节:
#define UART_BYTE *(char *)0x40008000 #define UART_SEND *(volatile char *)0x40008001 |= 0x08 void send_uart(char byte) { UART_BYTE = byte; // write byte to 0x40008000 address UART_SEND; // set bit number of address 0x40008001
}
send_uart函数的第一行代码可扩展为:
*(char *)0x40008000 = byte;
这一行代码是告诉编译器将值是0x40008000解释为一个指向char的指针,然后解除对该指针的定义(给出该指针所指向的值)(用最左边的*操作符),最后将字节值分配给该解除定义的指针。换句话说:把变量byte的值写到内存地址0x40008000。
将该函数的下一行代码扩展一下:
*(volatile char *)0x40008001 |= 0x08;
在这行代码中,我们对地址0x40008001和数值0x08(二进制的00001000,即第4位的1)进行了or位运算操作,并将结果存回地址0x40008001。换句话说:我们设置地址为0x40008001的字节的第4位。我们还声明地址为0x40008001的值是易失性的。这就告诉编译器,该值可能会被我们代码外部的进程所修改,所以编译器在写入该地址后不会对该地址的值做出任何假设。(在这种情况下,该字节在我们用软件设置后就被UART硬件取消了)。这些信息对于编译器的优化器来说是很重要的。例如,如果我们在for循环中这样做,而没有指定该值是易失性的,编译器可能会认为该值在被设置后永远不会改变,并在第一个循环后跳过执行该命令。
开发人员进行系统编程不能依赖的一个常见语言特性就是垃圾收集,甚至对一些嵌入式系统来说,只能进行动态分配。嵌入式应用程序在时间和内存资源方面非常有限。对于一些实时的嵌入系统,它们无法承受垃圾收集器的非确定性调用。如果因为内存不足而不能使用动态分配,那么拥有其他内存管理机制就显得尤为重要,比如将数据放在自定义地址中,就像C语言的指针所允许的那样。那些严重依赖动态分配和垃圾回收的语言不适用于资源紧张的系统。
C语言有一个非常小的运行时,其代码的内存占用要小于其它语言。例如与C++相比,一个由C语言生成的二进制文件,其体积大约是由类似的C++代码生成的二进制文件的一半。造成这种情况的主要原因之一是异常支持。
异常(Exceptions )机制是C++比C语言多出来的一个不错功能,如果异常不被触发和巧妙的实现,他们实际上是没有执行时间的开销,但代价便是增加代码体积。
下面让我们以C++代码为例:
// Class A declaration. Methods defined somewhere else;
class A
{
public:
A(); // Constructor
~A(); // Destructor (called when the object goes out of scope or is deleted)
void myMethod(); // Just a method
};
// Class B declaration. Methods defined somewhere else;
class B
{
public:
B(); // Constructor
~B(); // Destructor
void myMethod(); // Just a method
};
// Class C declaration. Methods defined somewhere else;
class C
{
public:
C(); // Constructor
~C(); // Destructor
void myMethod(); // Just a method
};
void myFunction()
{
A a; // Constructor a.A() called. (Checkpoint 1)
{
B b; // Constructor b.B() called. (Checkpoint 2)
b.myMethod(); // (Checkpoint 3)
} // b.~B() destructor called. (Checkpoint 4)
{
C c; // Constructor c.C() called. (Checkpoint 5)
c.myMethod(); // (Checkpoint 6)
} // c.~C() destructor called. (Checkpoint 7)
a.myMethod(); // (Checkpoint 8)
} // a.~A() destructor called. (Checkpoint 9)
该段代码中的A类、B类和C类中的方法都被定义在了外部(例如在其它文件中)。因此,编译器无法对它们进行解析,也不知道是否会抛出异常。所以程序必须准备处理从它们的任何构造函数、析构函数或其他方法调用中抛出的异常。解构器不应该抛出(做法非常糟糕),但用户还是可以抛出,或者他们可以通过调用一些抛出异常的函数或方法(显式或隐式)间接地抛出。
如果myFunction中的任何调用抛出了异常,堆栈解开机制必须能够调用所有已经构建的对象的析构器。堆栈解开机制的一个实现将使用这个函数的最后一次调用的返回地址来验证触发异常的调用的 "检查点编号"(这是简单的解释)。它是通过利用一个辅助的自动生成的函数(一种查找表)来实现的,当该函数的主体抛出异常时,该函数将被用于堆栈解绕,这将与此类似。
如果myFunction函数的任何一个调用抛出异常,C++的栈展开(stack unwinding)机制必须能够调用所有已构建对象的析构器。栈展开机制的一个实现是将使用这个函数的最后一次调用的返回地址来验证触发异常调用的 "检查点编号"(这是简单的解释)。它是通过利用一个辅助的自动生成函数(一种查找表)来实现,在该函数的主体抛出异常时,该函数将被用于堆栈解绕,与下面这段代码类似:
// Possible autogenerated function
void autogeneratedStackUnwindingFor_myFunction(int checkpoint)
{
switch (checkpoint)
{
// case 1 and 9: do nothing;
case 3: b.~B(); goto destroyA; // jumps to location of destroyA label
case 6: c.~C(); // also goes to destroyA as that is the next line
destroyA: // label
case 2: case 4: case 5: case 7: case 8: a.~A();
}
}
如果从case 1和9抛出异常,则没有对象需要销毁。对于case 3,则b和a必须被销毁。对于case 6,c和a必须被销毁。在所有情况下,销毁顺序必须得到尊重。对于检查点2、4、5、7和8,只有对象a需要被销毁。
这个辅助函数增加了代码的体积。这是C++添加到C语言中的空间开销的一部分。许多嵌入式应用无法负担这种额外的空间。因此,用于嵌入式系统的C++编译器通常有一个禁用异常的标志。在C++中禁用异常是不自由的,因为标准模板库严重依赖异常来告知错误。使用这种修改过的方案,没有异常,需要对C++开发人员进行更多的培训,以检测可能的问题或发现错误。
C++的一个原则就是“开发者无需为不使用的东西付费”。对于其他语言来说,二进制体积的增加会变得非常糟糕,通过其它功能来增加额外开销,虽然这些功能有用,但嵌入式系统却负担不起。虽然C语言不会给你提供这些额外功能,但他可以比其它语言拥有更紧凑的代码足迹(code footprint ),占用更小的磁盘空间。
C语言并不难学,作为一门老牌编程语言,有关它的教程跟学习资料非常多,那么学习C语言有哪些好处呢?
C语言是开发人员的通用语言,网上或者图书里面的不少算法都是基于C语言实现,这也为实现提供了最大的可移植性,开发者也会从中受益。
当我们与同事讨论代码的某些部分或其他语言的某些特征时,我们最终会 "用C语言说话":"这部分是向对象传递一个 "指针 "还是复制整个对象?这里会不会发生任何 "转换"?等等。
在分析高级语言的一部分代码的行为时,我们很少讨论(或思考)一部分代码正在执行的汇编指令。相反,在讨论机器在做什么时,我们可以用C语言描述(或想)得很清楚。
从大型数据库服务器或操作系统内核甚至是为了满足个人乐趣而制作的小型家用嵌入式应用,你都可以用C语言实现,并且还可以在网上找到相关Demo。Daniel呼吁大家,不要停止自己喜欢做的事情,比如学习C语言,它古老但小巧,并且是一门经过时间验证的编程语言。
当下许多编程语言在其预设的用途上都要优于C语言,但这并不意味着就能击败C,当考虑性能优先的时候,C依然是王者。世界正运行在C语言驱动的设备上,无论你是否意识到,你使用的诸多设备的的确确都用到了C语言。
原文链接:https://www.toptal.com/c/after-all-these-years-the-world-is-still-powered-by-c-programming
☞ 专辑 | 趣味设计模式
☞ 专辑 | 音视频开发
☞ 专辑 | C++ 进阶
☞ 专辑 | 超硬核 Qt
☞ 专辑 | 玩转 Linux
☞ 专辑 | GitHub源推荐
☞ 专辑 | 程序人生
关注公众号👇,一起优秀!
回复 进技术交流群,回复 获取海量学习资源。