我正在考虑学习C。
但是,如果人们可以“危险地”使用C(或C ++),为什么还要使用它呢?
危险是指指针和其他类似的东西。
就像堆栈溢出问题一样,为什么gets函数如此危险以至于不应该使用它?。为什么程序员不仅仅使用Java或Python或其他编译语言(如Visual Basic)?
我正在考虑学习C。
但是,如果人们可以“危险地”使用C(或C ++),为什么还要使用它呢?
危险是指指针和其他类似的东西。
就像堆栈溢出问题一样,为什么gets函数如此危险以至于不应该使用它?。为什么程序员不仅仅使用Java或Python或其他编译语言(如Visual Basic)?
Answers:
C早于您想到的许多其他语言。我们现在所知道的有关如何使编程“更安全”的很多知识都来自对C之类的语言的经验。
自C以来出现的许多安全语言都依赖更大的运行时,更复杂的功能集和/或虚拟机来实现其目标。结果,在所有流行/主流语言中,C仍然是“最低公分母”。
C是一种更容易实现的语言,因为它相对较小,即使在最弱的环境中也更有可能正常运行,因此许多需要开发自己的编译器和其他工具的嵌入式系统都更有可能提供功能性的编译器对于C。
由于C是如此之小和如此简单,因此其他编程语言倾向于使用类似C的API进行相互通信。这可能是C永远不会真正死亡的主要原因,即使我们大多数人仅通过包装与之交互也是如此。
许多试图在C和C ++上进行改进的“安全”语言并不是试图成为“系统语言”,它们几乎可以完全控制程序的内存使用和运行时行为。的确,如今越来越多的应用程序确实不需要这种级别的控制,但是总有少数情况是有必要的(特别是在虚拟机和浏览器中,这些虚拟机和浏览器实现了所有这些美观,安全的语言)我们其余的人)。
如今,有几种系统编程语言(Rust,Nim,D等)比C或C ++更安全。它们具有事后观察的优点,并且意识到在大多数情况下不需要这种精细控制,因此提供了一种通常安全的接口,其中包含一些在确实需要时可以切换到的不安全钩子/模式。
即使在C语言中,我们也学到了很多规则和准则,这些规则和准则往往会大大减少实践中出现的隐患。通常不可能获得追溯执行这些规则的标准,因为那样会破坏太多的现有代码,但是通常使用编译器警告,lint和其他静态分析工具来检测此类易于预防的问题。通过这些工具而获得成功的C程序子集已经比“仅仅C”要安全得多,并且如今任何有能力的C程序员都将使用其中的一些。
而且,您永远也不会像C语言混淆竞赛那样使Java语言混淆竞赛有趣。
首先,C是一种系统编程语言。因此,例如,如果您编写Java虚拟机或Python解释器,则需要使用系统编程语言来编写它们。
其次,C提供了Java和Python之类的语言无法提供的性能。通常,Java和Python中的高性能计算将使用以诸如C之类的高性能语言编写的库来完成繁重的工作。
第三,与Java和Python等语言相比,C的占用空间要小得多。这使得它可用于嵌入式系统,而嵌入式系统可能没有支持大型运行时环境和Java和Python等语言的内存需求所需的资源。
“系统编程语言”是一种适合用来构建工业强度系统的语言;就目前而言,Java和Python并不是系统编程语言。“究竟是什么使系统编程语言产生”不在此问题的范围内,但是系统编程语言确实需要为使用基础平台提供支持。
在另一方面(在响应评论),一个系统编程语言并没有需要自我托管。之所以出现此问题,是因为最初的问题是“人们为什么使用C”,第一个评论是“当您拥有PyPy时为什么要使用像C这样的语言”,我注意到PyPy 实际上确实使用了C。最初与该问题有关,但不幸的是(令人困惑的)“自我托管”与该答案实际上无关。对不起,我提出来了。
因此,总而言之:Java和Python不适合系统编程,不是因为它们的主要实现被解释了,或者不是因为本机编译的实现不是自托管的,而是因为它们没有提供使用底层平台的必要支持。
很抱歉添加另一个答案,但是我认为任何现有答案都不能直接解决您的第一句话:
“我正在考虑学习C”
为什么?您是否想做C通常用于当今的事情(例如设备驱动程序,VM,游戏引擎,媒体库,嵌入式系统,OS内核)?
如果是,那么是的,请确保根据您感兴趣的对象学习C或C ++。是否要学习它,以便对高级语言的功能有更深入的了解?
然后,您继续提及安全问题。您不一定需要对安全C 有深入的了解就可以执行安全C,就像使用高级语言的代码示例可能在没有准备好生产的情况下提供要点一样。
编写一些C代码以获得要点。然后放回架子上。除非您想编写生产 C代码,否则不必太担心安全性。
这是一个巨大的问题,有很多答案,但是简短的版本是每种编程语言都专门针对不同的情况。例如,用于Web的JavaScript,用于低级内容的C,用于Windows的C#等。当您知道编程知识以决定选择哪种编程语言后,它便会帮助您了解要执行的操作。
为了解决您的最后一点,为什么要使用C / C ++而不是Java / Python,它通常会降低速度。我制作游戏,而Java / C#最近才达到足以运行游戏的速度。毕竟,如果您希望游戏以每秒60帧的速度运行,并且希望您的游戏执行很多工作(渲染特别昂贵),那么您需要使代码尽可能快地运行。Python / Java / C#/许多其他程序都在“解释器”上运行,这是一个额外的软件层,可以处理C / C ++所不具备的所有繁琐的工作,例如管理内存和垃圾回收。额外的开销使事情变慢了,所以几乎您看到的每一个大型游戏都是在C或C ++中完成的(无论如何,在过去的十年中)。有例外:Unity游戏引擎使用C#*,而Minecraft使用Java,但这是例外,不是规则。一般来说,
*甚至Unity也不全是C#,其中很大一部分都是C ++,您只需将C#用作游戏代码即可。
编辑 为了回应我发表此文章后出现的一些评论:也许我过于简化了,我只是给出了一般的图片。使用编程,答案绝非易事。有针对C的解释器,Java语言可以在浏览器之外运行,而由于Mono,C#可以在几乎所有程序上运行。不同的编程语言专用于不同的领域,但是某个地方的某些程序员可能想出了如何使任何语言在任何上下文中运行的方法。由于OP似乎不了解太多编程知识(就我而言,这是假设,对不起,如果我错了),所以我试图将答案保持简单。
至于关于C#几乎与C ++一样快的评论,那里的关键词几乎是。当我上大学时,我们参观了许多游戏公司,而我的老师(一直鼓励我们整年从C#转向C ++)询问我们每家公司的程序员为何选择C ++而不是C#。说C#太慢了。通常,它运行速度很快,但是垃圾收集器会损害性能,因为您无法控制它的运行时间,并且如果您希望在建议的时间运行它,则它有权忽略您。如果您需要高性能的东西,那么您就不会想要如此不可预测的东西。
是的,为了回应我的“刚刚达到速度”的评论,是的,C#的速度提高大部分来自更好的硬件,但是随着.NET框架和C#编译器的改进,那里的速度有所提高。
关于“游戏使用与引擎相同的语言编写”的注释,这取决于。有些是,但许多是用多种语言编写的。虚幻引擎可以做UnrealScript和C ++,Unity可以做C#Javascript和Boo,许多其他用C或C ++编写的引擎都使用Python或Lua作为脚本语言。那里没有一个简单的答案。
仅仅因为它让我读到“谁在乎您的游戏以200fps或120fps运行”而烦恼我,如果您的游戏运行速度超过60fps,则可能是在浪费CPU时间,因为普通显示器甚至无法刷新该时间快速。一些高端和较新的设备可以,但是还不是标准的(还...)。
关于“忽略数十年的技术”的言论,我仍处于20年代初期,所以当我往后推算时,我主要是在呼应年长,经验丰富的程序员告诉我的内容。显然,这将在这样的网站上展开,但值得考虑。
有趣的是,您声称C不安全,因为“它具有指针”。反之亦然:Java和C#实际上只有指针(对于非本机类型)。Java中最常见的错误可能是Null Pointer Exception(参见https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare)。第二个最常见的错误可能是隐藏着对未使用对象的隐藏引用(例如,未丢弃的封闭式对话),因此无法释放这些引用,从而导致长时间运行的程序占用越来越大的内存。
有两种基本机制可以使C#和Java更安全,并且可以通过两种不同的方式更安全:
最新的C ++智能指针使程序员的工作变得更加轻松。
值得注意的是,精心设计的运行时间也存在安全风险。在我看来,由于新发现的安全问题,Oracle每两周更新一次JVM。当然,与单个程序相比,验证JVM 要困难得多。
因此,精心设计的运行时的安全性是模棱两可的,并且在一定程度上是骗人的:通过回顾和迭代,您的平均C程序可以变得相当安全。您的普通Java程序仅与JVM安全。也就是说,不是真的。决不。
gets()
您链接到的有关该文章的内容反映了历史上的图书馆决策,而今天这些决策将有所不同,而不是核心语言。
因为“安全”花费了速度,所以“更安全”的语言执行速度较慢。
您问为什么使用诸如C或C ++之类的“危险”语言,为什么有人用Python或Java等为您编写视频驱动程序等,并了解您对“安全性”的看法:)
但是,严重的是,您必须尽可能接近机器的核心内存,才能处理像素,寄存器等。Java或Python不能以任何类型的性能值得的速度做到这一点……C和C ++都允许您通过指针等执行此操作...
除了上述所有内容之外,还有一个非常常见的用例,它使用C作为其他语言的通用库。
基本上,几乎所有语言都具有C语言的API接口。
简单的例子,尝试为Linux / IOS / Android / Windows创建一个通用应用程序。除了现有的所有工具之外,我们最终要做的是用C语言编写一个核心库,然后为每种环境更改GUI,即:
我的两分钱
C语言的一个基本困难是,该名称用于描述语法相同但语义完全不同的多种方言。一些方言比其他方言安全得多。
在Dennis Ritchie最初设计的C语言中,C语句通常以可预测的方式映射到机器指令。因为C可以在发生有符号算术溢出之类的处理器时以不同的方式运行,所以不知道机器在发生算术溢出时的行为的程序员也不会知道在该机器上运行的C代码的行为,但是如果已知一台机器以某种方式工作(例如,无声的二进制补码环绕),则该机器上的实现通常也会这样做。C之所以以快速而闻名的原因之一是,在程序员知道平台在极端情况下的自然行为能够满足他们的需求的情况下,程序员或编译器无需编写代码来生成此类情况。 。
不幸的是,编译器作者认为,由于该标准对在这种情况下必须执行的操作没有任何要求(放宽是为了允许可能无法预期的硬件实现),因此编译器应该随意生成会否定律的代码时间和因果关系。
考虑类似:
int hey(int x)
{
printf("%d", x);
return x*10000;
}
void wow(int x)
{
if (x < 1000000)
printf("QUACK!");
hey(x);
}
超现代(但很时髦)的编译器理论建议编译器应输出“ QUACK!”。无条件地,因为在任何情况下条件为假,程序最终都将调用未定义的行为执行乘法,其结果将被忽略。由于标准在这种情况下允许编译器执行其喜欢的任何事情,因此它允许编译器输出“ QUACK!”。
虽然C过去比汇编语言更安全,但是当使用超现代编译器时,情况恰恰相反。在汇编语言中,整数溢出可能导致计算产生无意义的结果,但是在大多数平台上,这将是其影响的程度。如果结果最终还是被忽略了,那么溢出将无关紧要。但是,在超现代C语言中,即使通常是未定义行为的“良性”形式(例如计算中的整数溢出最终被忽略)也可能导致任意程序执行。
int arr[5][[5]
,给定访问尝试arr[0][5]
将产生未定义行为。这样的规则使编译器arr[1][0]=3; arr[0][i]=6; arr[1][0]++;
可以推断出arr[1][0]
等于4的值,而无需考虑的值i
。
什么是“危险”?
在语言火焰大战中(经常是与Java相比),C是“危险”的说法是经常谈论的话题。但是,这一说法的证据尚不清楚。
C是一种具有特定功能的语言。其中某些功能可能会允许某些其他类型的语言所不允许的某些类型的错误(通常会突出显示C的内存管理风险)。但是,这与C 总体上比其他语言更危险的说法不同。我不知道有人在这一点上提供令人信服的证据。
同样,“危险”取决于上下文:您要做什么,以及您担心哪种风险?
在许多情况下,我认为C比高级语言更“危险”,因为C需要您对基本功能进行更多的手动实现,从而增加了发生错误的风险。例如,使用C语言进行一些基本的文本处理或开发网站通常很愚蠢,因为其他语言的功能使其变得更加容易。
但是,C和C ++被广泛用于关键任务系统,因为在这种情况下,具有更直接控制硬汉的较小语言被认为“更安全”。从一个很好的堆栈溢出答案:
尽管C和C ++并非专门针对此类应用程序而设计,但出于多种原因,它们被广泛用于嵌入式和安全性至关重要的软件。注释的主要属性是对内存管理的控制(例如,它可以避免垃圾回收),简单,调试良好的核心运行时库以及成熟的工具支持。今天使用的许多嵌入式开发工具链最早是在1980年代和1990年代开发的,当时这是一种最新技术,并且来自当时流行的Unix文化,因此这些工具在此类工作中仍然很流行。
尽管必须仔细检查手动内存管理代码以避免错误,但是它可以一定程度地控制应用程序响应时间,而依赖于垃圾回收的语言则无法提供这种控制。C和C ++语言的核心运行时库相对简单,成熟并且易于理解,因此它们是可用的最稳定的平台之一。
请允许我重新表述您的问题:
我正在考虑学习[工具]。
但是,如果[危险]可以使用[工具](或[相关工具]),人们为什么要使用它们?
任何有趣的工具都可能会危险地使用,包括编程语言。您可以学到更多,以便做更多的事情(使用该工具可以减少危险)。尤其是,您将学习该工具,以便您可以做该工具所擅长的事情(并可能会认识到该工具何时是您所知道的工具中最好的工具)。
例如,如果您需要在一块木头上放置一个直径6毫米,深5厘米的圆柱孔,则钻头比LALR解析器好得多。如果您知道这两个工具是什么,就知道哪个是正确的工具。如果您已经知道如何使用钻头,瞧!
C只是另一个工具。对于某些任务,它比对其他任务要好。这里的其他答案解决了这个问题。如果您学习了一些C语言,您将认识到何时是正确的工具,何时才是正确的工具。
我正在考虑学习C
没有特定的理由不学习C语言,但我建议使用C ++语言。它提供了C所做的很多工作(因为C ++是C的超集),带有大量的“附加”。在C ++之前学习C是不必要的-它们实际上是独立的语言。
换句话说,如果C是一组木工工具,则可能是:
您可以使用这些工具来构建任何东西,但是任何好的东西都可能需要大量的时间和技巧。
C ++是您本地硬件商店中的电动工具的集合。
如果您坚持使用基本的语言功能,那么C ++的额外学习曲线就很少。
但是,如果人们可以“危险地”使用C(或C ++),为什么还要使用它呢?
因为有些人不想要宜家的家具。=)
严重的是,尽管许多比C或C ++“更高”的语言可能具有使它们(可能)“更容易”在某些方面使用的功能,但这并不总是一件好事。如果您不喜欢某项工作的完成方式或不提供功能,那么您可能无能为力。另一方面,C和C ++提供了足够的“低级”语言功能(包括指针),您可以直接直接访问许多内容(尤其是硬件或操作系统方面的内容),也可以自己构建它,而这在其他情况下是不可能的。语言已实施。
更具体地说,C具有以下一些功能,这些功能对于许多程序员来说都是理想的:
兼容性 -C已经存在很长时间了,每个人都有相应的工具和库。语言本身也不是挑剔的-它期望处理器执行指令和内存来保存东西,仅此而已。
此外,还有一种称为应用程序二进制接口(ABI)的东西。简而言之,这是程序在机器代码级别进行通信的一种方法,与应用程序编程接口(API)相比,它可以具有很多优势。尽管其他语言(例如C ++)可以具有ABI,但是通常它们与C的统一性(商定的)不那么统一,因此当您出于某种原因要使用ABI与另一个程序进行通信时,C是一种很好的基础语言。
为什么程序员不仅仅使用Java或Python或其他编译语言(如Visual Basic)?
效率(有时需要在没有相对直接访问内存的情况下才能实现的内存管理方案)。
当您可以将肮脏的爪子直接放在内存小孔中的零零零碎的地方,而不必等待那意味着老师直接分发玩具时,用指针直接访问内存会引入很多巧妙的技巧(通常是快速的)在游戏时,再将它们them起。
简而言之,添加内容可能会导致滞后或以其他方式引入不必要的复杂性。
关于脚本语言和类似的语言,您必须努力工作,以使要求辅助程序运行的语言能够像C(或任何编译语言)本机一样高效地运行。由于要在混合中添加另一个程序,因此添加动态解释器会固有地降低执行速度并增加内存使用率。您的程序效率很大程度上取决于此辅助程序的效率,以及编写原始程序代码的性能(差)。更不用说您的程序通常完全依赖于第二个程序才能执行。由于特定原因,第二个程序在特定系统上不存在?代码不行。
实际上,引入任何 “额外”的内容都可能减慢或使您的代码复杂化。在“没有可怕的指针”的语言中,您总是在等待其他代码清除在您身后,或者以其他方式找出“安全”的处理方式-因为您的程序仍在执行与可能执行的相同的内存访问操作指针。您不是处理它的那个人(所以您不能搞定它,genius = P)。
危险是指指针和其他类似的东西。[...]像堆栈溢出问题一样,为什么gets函数如此危险以至不应该使用它?
根据公认的答案:
“直到1999 ISO C标准,它仍然是该语言的正式组成部分,但在2011年标准中正式将其删除。大多数C实现仍支持该语言,但至少gcc对使用它的任何代码都发出警告。”
因为某些事情可以用某种语言完成,所以必须做到这一点是愚蠢的。语言具有修复的缺陷。出于与旧代码兼容的原因,仍可以使用此构造。但是没有任何(可能)强迫程序员使用gets()的方法,实际上,实际上该命令已被更安全的替代方法替代。
更重要的是,gets()的问题本身并不是指针问题。这是一个命令的问题,它不一定知道如何安全地使用内存。从抽象的意义上讲,这就是所有指针问题-读和写你不希望的东西。指针不是问题。这是指针实现的问题。
需要澄清的是,在您意外访问了您不打算访问的存储位置之前,指针并不危险。即使如此,也不能保证您的计算机会融化或爆炸。在大多数情况下,您的程序将停止运行(正确)。
就是说,由于指针提供对内存位置的访问,并且因为数据和可执行代码一起存在于内存中,所以要正确管理内存确实存在意外损坏的真正危险。
到那时,由于真正的直接内存访问操作通常所提供的利益通常比几年前少,因此即使是非垃圾收集的语言(如C ++)也引入了诸如智能指针之类的东西,以帮助弥合内存效率和安全性之间的差距。
总之,只要安全使用指针,几乎没有理由担心指针。只是从南方公园的史蒂夫“鳄鱼猎人”欧文(Steve Steve )中获得一些启示 - 不要将拇指伸入鳄鱼的洞中。
与往常一样,编程语言只是解决问题的结果。实际上,您不仅应该学习C语言,还应该学习许多不同的语言(以及其他编程计算机的方法,包括GUI工具或命令解释器),以便在解决问题时使用一个不错的工具箱。
有时,您会发现问题很容易导致Java默认库中包含的某些问题,在这种情况下,您可以选择Java以加以利用。在其他情况下,可能需要在Windows上执行.NET运行时中要简单得多的操作,因此您可以使用C#或VB。可能有一个图形工具或命令脚本可以解决您的问题,然后可以使用它们。也许您需要在多个平台上编写GUI应用程序,考虑到JDK中包含的库,可以选择Java,但是,又一次,一个目标平台可能缺少JRE,因此您可能选择了C和SDL(或类似的名称)。
C在此工具集中具有重要的地位,因为它通用,小巧,快速并且可以编译为机器代码。在阳光下的每个平台上也都支持它(但是不是重新编译)。
最重要的是,您应该学习尽可能多的工具,语言和范例。
请远离思维定式:“我是X程序员”(X = C,C ++,Java等)
只需使用“我是程序员”即可。
程序员通过指示机器执行工作负载来解决问题并设计算法。故事结局。这与语言无关。您最重要的技能是解决问题和对结构化问题进行逻辑分解,语言技能/选择始终是次要的和/或问题性质的结果。
如果您对C感兴趣,一条有趣的方法是使用Go扩展技能。Go确实是经过改进的C,具有垃圾回收和接口,以及不错的内置线程模型/通道,它还带来了C的许多优点(例如指针算术和编译为机器代码)。
这取决于您打算如何处理。C被设计为汇编语言的替代,并且是最接近于机器语言的高级语言。因此,它在大小和性能上的开销很低,并且适合于系统编程和其他需要占用空间小且与底层硬件接近的任务。
当您在位和字节级别上工作时,内存是原始的同类数据集合,通常需要有效地实现最高效的分配器和数据结构,因此没有安全性。安全性主要是与数据类型相关的概念,并且内存分配器不适用于数据类型。它与位和字节一起使用,以汇集那些相同的位和字节,这些位和字节可能在某一时刻代表一种数据类型,而在稍后又代表另一种数据类型。
在这种情况下是否使用C ++都没有关系。你还是会洒static_casts
在整个代码从投void*
三分球,并仍与比特和字节的工作,只是处理有关在这方面比C具有更简单的类型系统,您可以自由尊重型系统更多的麻烦到memcpy
比特和字节左右,而不必担心在推土类型系统。
实际上,在这样的低级位和字节上下文中,使用整体安全的语言C ++编写代码比编写C语言中的代码更危险,通常会更困难,因为您可能会推翻C ++的类型系统并执行类似的操作覆盖vptrs,并且无法在适当的时间调用复制构造函数和析构函数。如果您花适当的时间尊重这些类型,并使用新的Placement并手动调用dtor等,那么您会在RAII不太实用的低级环境中接触到异常处理的世界,并实现异常-在如此低级的环境中,安全性是非常困难的(您必须假装几乎任何函数都可以抛出并捕获所有可能性,并将任何副作用作为不可分割的事务回滚,就像什么也没有发生一样)。C代码通常可以“
而且不可能用不允许您在此处“危险”的语言来实现这样的分配器。您将不得不依靠他们提供的任何分配器(最有可能在C或C ++中实现),并希望它足以满足您的目的。而且,几乎总是有效率更高,但通用性较低的分配器和数据结构适合您的特定用途,但适用性却要窄得多,因为它们是专门为您的目的量身定制的。
大多数人不需要C或C ++之类的东西,因为他们可以调用最初用C或C ++实现的代码,甚至可能已经为它们实现的汇编。许多人可能会从高水平的创新中受益,例如将仅使用已在C中实现的现有图像处理功能库的图像程序串起来,而在通过单个像素循环的最低水平上他们并没有进行太多创新,但是提供了前所未有的非常友好的用户界面和工作流程。在那种情况下,如果软件的目的只是要对低级库进行高层调用(“为我处理整个图像,而不是为每个像素做某事”),那么它可能是过早的优化甚至试图开始用C编写这样的应用程序。
但是,如果您在低级别上做一些新的事情,它可以帮助以低级别的方式访问数据,例如以前从未见过的全新图像过滤器,那么它的速度足以实时处理高清视频,那么您通常必须获得有点危险。
这东西理所当然是容易的。我记得有人在Facebook上发帖,指出有人用Python创建3D视频游戏是可行的,这暗示着低级语言已经过时了,这肯定是一款外观不错的游戏。但是Python正在对用C实现的库进行高层调用,以完成所有繁重的工作。您不能仅通过对现有库进行高层调用来制作虚幻引擎4。虚幻引擎4 是图书馆。它完成了其他所有库和引擎中从未存在过的工作,从照明到其节点蓝图系统,以及它如何实时编译和运行代码。如果您想在低引擎/核心/内核级别上进行创新,那么您必须获得低级别的支持。如果所有游戏开发人员都切换到高级安全语言,则不会有虚幻引擎5、6或7。很可能是人们在四十年后仍在使用虚幻引擎,因为您无法在即将到来的水平上进行创新只需对旧版本进行高级调用即可使用下一代引擎。