C(编程语言)

C
Text says "The C Programming Language"
C编程语言书的封面图。
范例 多范式命令程序),结构化
设计 丹尼斯·里奇(Dennis Ritchie)
开发人员 ANSI X3J11( ANSI C ); ISO / IEC JTC 1(联合技术委员会1) / SC 22(小组委员会22) / WG 14(工作组14)(ISO C)(ISO C)
首先出现 1972
稳定版本
C17 / 2018年6月
预览发布
C23N3096 ) / 2023年4月2日
打字学科 静态虚弱明显名义
作业系统 跨平台
文件名扩展 .c,.h
网站 www.iso.org/standard/74528.html www.open-std.org/jtc1/sc22/wg14/
主要实施
PCCGCCClangIntel CC ++建造者Microsoft Visual C ++Watcom C
方言
旋风统一并行C拆分CCILKC*
被影响
BBCPLCPL ), Algol 68PL/IFortran
受影响
无数: amplawkcshc ++c-c#objective-cdgojavajavacriptjs ++朱莉娅limbolpc ,perl, perlphp ,php,php, php,php,php,php,python, python ,python,rust,rust,rust,seed7,seed7,seed7,seed7, seed7 , seed7seed7 , seed7 , seed7 ,seed7, seed7 V(Vlang)ValaVerilog ( HDL), NimZig
  • c编程在Wikibooks

c发音- 就像字母c一样)是一种通用计算机编程语言。它是由丹尼斯·里奇(Dennis Ritchie)于1970年代创建的,并且仍然广泛使用和影响力。根据设计,C的功能可干净地反映目标CPU的功能。它发现在操作系统设备驱动程序协议堆栈中使用了持久的用途,但是其在应用程序软件中的使用一直在减少。 C通常用于从最大的超级计算机到最小的微控制器嵌入式系统的计算机架构。

C编程语言B的继任者C最初是在1972年至1973年间由Ritchie在贝尔实验室开发的,以构建在Unix上运行的实用程序。它用于重新实现UNIX操作系统的内核。在1980年代,C逐渐越来越受欢迎。它已成为使用最广泛的编程语言之一,其C编译器几乎可用于所有现代计算机架构和操作系统。由原始语言设计师合著的书C编程语言已成为该语言的事实上的标准多年。自1989年以来,美国国家标准研究所(ANSI)和国际标准化组织(ISO)已经标准化。

C是一种命令性的程序语言,具有静态类型系统的结构化编程词汇可变范围递归。它旨在编译以提供对记忆和语言构造的低级访问,这些内存和语言结构有效地映射到机器指令,所有这些都具有最少的运行时支持。尽管具有低水平的功能,该语言旨在鼓励跨平台编程。标准组合的C程序编写了带有可移植性的C程序,可以针对多种计算机平台和操作系统进行编译,其源代码几乎没有更改。

自2000年以来,C一直在TIOBE索引中排名前两种语言,这是对编程语言的普及。

概述

C编程语言的发明者Dennis Ritchie (右)与Ken Thompson

C是Algol传统中的必要,程序语言。它具有静态类型系统。在C中,所有可执行代码都包含在子例程中(也称为“功能”,尽管不是功能编程的意义)。功能参数按值传递,尽管数组作为指针传递,即数组中的第一个项目的地址。 C中通过明确将指针传递给所引用的东西,在C中模拟通过引用

C程序源文本是自由形式的代码。半隆终止语句,而卷曲括号用于将语句分为

C语言还表现出以下特征:

  • 该语言具有少数固定数量的关键字,其中包括完整的控制流原始图:if/else,for,do/while,while, 和switch。用户定义的名称与任何类型的sigil均未与关键字区分开。
  • 它具有大量的算术,位和逻辑运算符:+,+=,++,&,||, ETC。
  • 可以在单个语句中执行多个分配
  • 功能:
    • 如果不需要,函数返回值可以忽略。
    • 功能和数据指针允许临时运行时多态性
    • 在其他功能的词汇范围内可能无法定义函数。
    • 可以在范围内定义变量。
    • 一个函数可以自称为自身,因此支持递归
  • 数据键入是静态的,但执行弱;所有数据都有一种类型,但是隐式转换是可能的。
  • 用户定义(键入)和复合类型是可能的。
    • 异构汇总数据类型(struct)允许访问相关的数据元素并分配为单位。无法使用单个内置操作员比较整个结构的内容(必须单独比较元素)。
    • 工会是一个具有重叠成员的结构;它允许多种数据类型共享相同的内存位置。
    • 阵列索引是辅助表示法,根据指针算术定义。无法使用单个内置操作员分配或比较整个阵列。使用或定义中没有“数组”关键字;相反,方括号表示数组句法,例如month[11].
    • 列举类型是可能的enum关键词。它们与整数可以自由互动。
    • 字符串不是独特的数据类型,而是通常以零端的字符阵列实现
  • 通过将计算机地址转换为指针,可以使用低级对计算机存储器的访问。
  • 过程(子例程未返回值)是功能的特殊情况,带有空的返回类型void.
  • 可以将内存分配给带有库例程的程序。
  • 预处理器执行定义,源代码文件包含和条件汇编
  • 模块化有一种基本形式:可以单独编译文件并将其链接在一起,并控制哪些功能和数据对象可以通过通过哪些函数和数据对象通过staticextern属性。
  • 诸如I/O字符串操纵和数学功能之类的复杂功能始终委派给库例程
  • 汇编后生成的代码在基础平台上具有相对简单的需求,这使其适合于创建操作系统和在嵌入式系统中使用。

尽管C不包含其他语言中发现的某些功能(例如对象方向垃圾收集),但通常可以通过使用外部库(例如, Glib对象系统Boehm垃圾收集器)来实现或模拟这些功能。

与其他语言的关系

Many later languages have borrowed directly or indirectly from C, including C++ , C# , Unix's C shell , D , Go , Java , JavaScript (including transpilers ), Julia , Limbo , LPC , Objective-C , Perl , PHP , Python , Ruby , RustSwiftVerilogSystemVerilog (硬件说明语言)。这些语言绘制了许多C的控制结构和其他基本功能。其中大多数(Python是一种戏剧性的例外)也表达了与C高度相似的语法,并且它们倾向于将C与基本类型的可识别表达和语句语法结合在一起系统,数据模型和语义可能会完全不同。

历史

早期发展

C语言的时间表
非正式名称 ISO/IEC标准
1972
1978 K&R C
1989/1990 ANSI C / ISO C 9899:1990
1999 C99 9899:1999
2011 C11 ,C1X 9899:2011
2018 C17 9899:2018
2024* C23 ,C2X 9899:2023

C的起源与Unix操作系统的开发密切相关,Unix操作系统最初由Dennis RitchieKen ThompsonPDP-7上以汇编语言实施,并结合了同事的几个想法。最终,他们决定将操作系统移植到PDP-11 。原始的PDP-11版本的UNIX也以汇编语言开发。

B

汤普森(Thompson)想要一种编程语言,用于开发新平台的实用程序。起初,他试图写一个Fortran编译器,但很快就放弃了这个想法。取而代之的是,他创建了一个名为BCPL的最近开发的系统编程语言的剪切版本。当时没有BCPL的官方描述,Thompson将语法修改为不那么言行,并且类似于简化的Algol ,称为Smalgol。汤普森称结果为b 。他将B描述为“带有许多Smalgol语法的BCPL语义”。像BCPL一样,B有一个自举编译器,以便利移植到新机器上。但是,最终很少有公用事业在B中写成,因为它太慢了,无法利用PDP-11功能(例如字节可寻址性)。

新B和第一个C版本

1971年,里奇(Ritchie)开始改善B,以利用更强大的PDP-11的功能。一个重要的增加是字符数据类型。他称这个新B (NB)。汤普森(Thompson)开始使用NB编写UNIX内核,他的要求塑造了语言发展的方向。到1972年,添加了更丰富类型的NB语言:NB具有intchar。还添加了指针,即将指针生成其他类型,所有类型的数组以及要从功能返回的类型的能力。表达式中的阵列变成了指针。编写了一个新的编译器,该语言被更名。

C编译器及其制造的一些实用程序包含在2版UNIX中,该版本也称为研究Unix

结构和UNIX内核重写

在1973年11月发布的第4版Unix上, Unix内核已在C中广泛重新启动C。struct类型。

预处理器于1973年左右在艾伦·斯奈德(Alan Snyder)的敦促下引入,并认识到BCPL和PL/I中可用的文件包含机制的实用性。其原始版本仅包括文件和简单的字符串替换:#include#define无参数宏。此后不久,它主要由Mike Lesk ,然后由John Reiser扩展,以将宏与参数和有条件的汇编结合在一起。

Unix是以汇编以外的其他语言实现的第一个操作系统内核之一。较早的实例包括Multics系统(用PL/I编写)和Burroughs B5000MCP )(MCP)(MCP)(于1961年用Algol编写)。1977年左右,Ritchie和Stephen C. Johnson对促进UNIX操作系统可移植性的语言。 Johnson的便携式C编译器是新平台上几个C实现的基础。

K&R C

Brian KernighanDennis Ritchie撰写的《 C编程语言》一书的封面

1978年, Brian KernighanDennis Ritchie出版了C编程语言的第一版。该书从其作者的名字缩写中称为K&R ,作为该语言的非正式规范。它描述的C版本通常称为“ K&R C ”。由于1978年发布,它也称为C78 。该书的第二版涵盖了后来的ANSI C标准,如下所述。

K&R介绍了几种语言功能:

  • 标准I/O库
  • long int数据类型
  • unsigned int数据类型
  • 表格的复合分配运营商=op(例如=-)更改为表格op=(那是,-=)消除诸如构造所产生的语义歧义i=-10,被解释为i =- 10(减少i到10)而不是可能的意图i = -10(让i为-10)。

即使在1989年的ANSI标准发布之后,多年来,K&R C仍被认为是C程序员在需要最大的可移植性时限制自己的“最低共同点”,因为许多较老的编译器仍在使用,并且因为仔细编写了K&R的K&R C代码也可以是法律标准C。

在C的早期版本中,只有返回类型以外的函数int如果在函数定义之前使用,则必须声明;假定没有事先声明的功能以返回类型int.

例如:

long some_function(); /* This is a function declaration, so the compiler can know the name and return type of this function. */
/* int */ other_function(); /* Another function declaration.  Because this is an early version of C, there is an implicit 'int' type here.  A comment shows where the explicit 'int' type specifier would be required in later versions. */
/* int */ calling_function() /* This is a function definition, including the body of the code following in the { curly brackets }. Because no return type is specified, the function implicitly returns an 'int' in this early version of C. */
{
    long test1;
    register /* int */ test2; /* Again, note that 'int' is not required here. The 'int' type specifier */
                              /* in the comment would be required in later versions of C. */
                              /* The 'register' keyword indicates to the compiler that this variable should */
                              /* ideally be stored in a register as opposed to within the stack frame. */
    test1 = some_function();
    if (test1 > 1)
          test2 = 0;
    else
          test2 = other_function();
    return test2;
}

int可以在K&R C中省略评论的类型说明符,但在以后的标准中需要。

由于K&R函数声明未包含有关函数参数的任何信息,因此未执行函数参数类型检查,尽管如果某些编译器将局部函数拨打了错误的参数数量,或者是否有多个呼叫对外部函数,则某些编译器会发出警告消息。使用了不同数字或类型的参数。开发了单独的工具,例如Unix的绒毛实用程序(除其他外)可以检查多个源文件的功能使用的一致性。

在出版K&R C发布后的几年中,该语言添加了几个功能,并由AT&T(特别是PCC )和其他一些供应商的编译器支持。其中包括:

  • void函数(即,函数无返回值)
  • 函数返回struct或者union类型(以前只能返回单个指针,整数或浮点)
  • 分配struct数据类型
  • 枚举类型(以前使用了整数固定值的预处理器定义,例如#define GREEN 3)

标准库上的大量扩展和缺乏协议,以及语言的普及,即使是UNIX编译器也没有精确地实施K&R规范的事实,也导致了标准化的必要性。

ANSI C和ISO C

在1970年代后期和1980年代后期,随着其受欢迎程度开始显著提高,用于各种大型机微型计算机微型计算机(包括IBM PC)的各种C版本。

1983年,美国国家标准研究所(ANSI)成立了X3J11委员会,以建立基于C. X3J11的标准规范,该规范基于UNIX实施的C标准;但是,Unix C库的不可存储部分已移交给IEEE工作组1003,成为1988年POSIX标准的基础。 1989年,C标准被批准为ANSI X3.159-1989“编程语言C”。该语言的此版本通常称为ANSI C ,标准C或有时C89。

1990年,国际标准化组织(ISO)(ISO)采用了ANSI C标准(具有格式更改)为ISO/IEC 9899:1990,有时称为C90。因此,术语“ C89”和“ C90”是指相同的编程语言。

与其他国家标准机构一样,ANSI不再独立开发C标准,而是由ISO/IEC JTC1/SC22 /WG14维护的国际C标准辩护。国际标准的全国采用通常发生在ISO出版的一年之内。

C标准化过程的目的之一是产生K&R C的超集,并结合了许多随后引入的非官方特征。标准委员会还包括一些其他功能,例如功能原型(从C ++借来),void指针,对国际角色集地区的支持以及预处理人的增强功能。尽管增加了参数声明的语法,以包括C ++中使用的样式,但仍允许K&R接口继续使用,以与现有的源代码兼容。

C89得到当前C编译器的支持,大多数现代C代码都基于它。任何仅在标准C中编写的程序,并且没有任何与硬件有关的假设编写的程序,都将在其资源限制内正确运行,并在具有符合C实现的任何平台上运行。没有这样的预防措施,程序只能在某个平台或特定的编译器上进行编译,例如由于使用非标准库(例如GUI库),或依赖于编译器或平台特定属性之类的依赖作为数据类型和字节的确切大小。

如果必须通过标准符合条件或基于K&R C的编译器来编译代码,则__STDC__宏可用于将代码分为标准和K&R部分,以防止仅在标准C中可用的功能的基于K&R C的编译器上使用。

在进行ANSI/ISO标准化过程之后,C语言规范在数年内保持相对静态。 1995年,发布了1990 C标准的规范修正案(ISO/IEC 9899/AMD1:1995,非正式地称为C95),以纠正一些细节并为国际角色集增加更多的支持。

C99

C标准在1990年代后期进一步修订,导致ISO/IEC 9899:1999在1999年发布,通常称为“ C99 ”。从那以后,它已被技术Crigenda修改了三次。

C99引入了几个新功能,包括内联功能,几种新数据类型(包括long long intcomplex类型表示复数),可变长度阵列灵活数组成员,对IEEE 754浮点的支持得到改进//,如BCPL或C ++。其中许多已经在几个C编译器中作为扩展实施。

C99在大多数情况下与C90兼容,但在某些方面更加严格。特别是,缺少类型规范的声明不再具有int隐含地假设。标准宏__STDC_VERSION__定义为价值199901L指示可以使用C99支持。 GCCSolaris Studio和其他C编译器现在支持C99的许多新功能。但是, Microsoft Visual C ++中的C编译器实现了与C ++ 11所需的C89标准和C99的部分。

此外,C99标准需要使用Unicode以Escaped字符的形式支持标识符(例如\u0040或者\U0001f431)并建议支持原始Unicode名称。

C11

在2007年,开始对C标准的另一项修订开始,非正式地称为“ C1X”,直到其正式出版ISO/IEC 9899:2011:2011-12-08。 C标准委员会通过了指南,以限制现有实施未测试的新功能的采用。

C11标准为C和库增加了许多新功能,包括类型的通用宏,匿名结构,改进的Unicode支持,原子操作,多线程和界限检查功能。它还使现有C99库的某些部分可选,并提高了与C ++的兼容性。标准宏__STDC_VERSION__被定义为201112L指示可以提供C11支持。

C17

2018年6月出版为ISO/IEC 9899:2018,C17是C编程语言的当前标准。它没有引入新的语言功能,只有技术更正和澄清C11中的缺陷。标准宏__STDC_VERSION__被定义为201710L.

C23

C23是下一个(C17之后)主要C语言标准修订的非正式名称。预计将于2024年发布。

嵌入c

从历史上看,嵌入式C编程需要对C语言的非标准扩展,以支持诸如定点算术,多个不同的内存库和基本I/O操作之类的异国情调。

2008年,C标准委员会发布了一份技术报告,该报告扩展了C语言,通过为所有实施提供了共同的标准来解决这些问题。它包括普通C中没有的许多功能,例如定点算术,命名地址空间和基本I/O硬件地址。

句法

C具有由C标准指定的正式语法。线结尾通常在C中不显著;但是,在预处理阶段,线界确实具有显著性。评论可能会出现在定界符之间/**/,或(自C99以来)以下//直到线结束。评论被划定/**/请勿嵌套,如果字符序列出现在字符串或字符文字中,则不会将其解释为注释定界符。

C源文件包含声明和功能定义。功能定义又包含声明和语句。声明要幺使用关键字来定义新类型struct,union, 和enum,或将类型分配给新变量的类型,也许是保留存储,通常是通过编写类型,后跟变量名称。关键字,例如charint指定内置类型。代码部分封闭在括号中({},有时称为“卷曲括号”),以限制声明范围,并充当控制结构的单一陈述。

作为一种命令式语言,C使用语句指定操作。最常见的陈述是一个表达式语句,由要评估的表达式组成,然后是半隆。作为评估的副作用,可以调用函数,并且可以为变量分配新值。为了修改语句的正常顺序执行,C提供了多个由保留关键字标识的控制流语句。结构化编程if... [else]有条件执行和do...while,while, 和for迭代执行(循环)。这for陈述具有单独的初始化,测试和重新定性表达式,可以省略任何或全部。breakcontinue可以在循环中使用。休息时间用于离开最内在的循环陈述,并继续使用它来重新引导。还有一个非结构化goto声明哪些直接分支到该功能中指定的标签switch选择acase根据整数表达式的值执行。与许多其他语言不同,Control-Flow将落在下一种case除非被break.

表达式可以使用各种内置运算符,并且可能包含功能调用。未指定对大多数操作员的函数和操作数的论点和操作数的顺序。评估甚至可以交错。但是,所有副作用(包括变量的存储)将发生在下一个“序列点”之前。序列点包括每个表达式语句的结尾,以及每个函数调用的输入和返回。在评估包含某些操作员的表达式期间也发生了序列点(&&,||,?:逗号操作员)。这允许编译器的高度对象代码优化,但要求C程序员比其他编程语言需要更多的谨慎获得可靠的结果。

克尼根(Kernighan)和里奇(Ritchie)在引入C编程语言中说:“ C与其他任何语言一样,也有瑕疵。一些操作员的优先次数错误;语法的某些部分可能会更好。” C标准没有试图纠正许多此类瑕疵,因为此类更改对已经存在的软件的影响。

字符集

基本C源字符集包括以下字符:

newline表示文本行的结束;它不必与实际的单个字符相对应,尽管为了方便起见,C将其视为一个字符。

其他多字节编码字符可以用字符串文字使用,但并非完全可移植。最新的C标准( C11 )允许通过使用C源文本中的多国Unicode字符嵌入到C源文本中\uXXXX或者\UXXXXXXXX编码(在哪里X尽管尚未广泛实施此功能,但表示十六进制的字符)。

基本c执行字符集包含相同的字符,以及警报backspace运输返回的表示形式。随着C标准的每个修订版,对扩展字符集的运行时间支持都增加了。

保留的单词

C89有32个保留单词,也称为关键字,这些单词是除了它们被预先定义的单词以外的任何目的都无法使用的单词:

C99保留了五个单词:

C11保留了七个单词:

  • _Alignas
  • _Alignof
  • _Atomic
  • _Generic
  • _Noreturn
  • _Static_assert
  • _Thread_local

C23将保留14个单词:

  • alignas
  • alignof
  • bool
  • constexpr
  • false
  • nullptr
  • static_assert
  • thread_local
  • true
  • typeof
  • typeof_unqual
  • _Decimal128
  • _Decimal32
  • _Decimal64

最近保留的大多数单词以下划线开始,然后是大写字母,因为该表格的标识符先前是由C标准保留的,仅由实施使用。由于现有的程序源代码不应使用这些标识符,因此当C实施开始支持对编程语言的这些扩展时,它不会受到影响。一些标准标头确实为强调标识符定义了更方便的同义词。其中一些单词被添加为关键字,并在C23中使用其常规拼写,并删除了相应的宏。以前的语言包括一个称为的保留词entry,但这很少实施,现在已被作为保留词被删除。

操作员

C支持一组丰富的操作员,这些符号是表达式中用于指定在评估该表达式时要执行的操作的符号。 C有:

C使用操作员=(在数学中用于表达平等)表示分配,遵循FortranPL/I的先例,但与Algol及其衍生物不同。 C使用操作员==测试平等。这两个操作员(分配和平等)之间的相似性可能会导致一个代替另一个操作员,并且在许多情况下,错误不会产生错误消息(尽管有些编译器会产生警告)。例如,条件表达式if (a == b + 1)可能会错误地写成if (a = b + 1),如果a分配后不是零。

C运营商的优先级并不总是直观的。例如,操作员==绑定比操作员(在执行之前执行)更紧密&(位和)和|(比特或)在诸如x & 1 == 0,必须写为(x & 1) == 0如果那是编码员的意图。

“你好,世界”例子

“你好世界!” Brian Kernighan的计划(1978)

出现在K&R第一版中的“ Hello,World ”示例已成为大多数编程教科书中介绍程序的模型。该程序将“ Hello,World”打印到标准输出,该输出通常是终端或屏幕显示。

原始版本是:

main()
{
    printf("hello, world\n");
}

标准配合的“ Hello,World”程序是:

#include <stdio.h>
int main(void)
{
    printf("hello, world\n");
}

该程序的第一行包含一个预处理指令,由#include。这导致编译器用整个文本替换该行stdio.h标准标头,其中包含标准输入和输出功能的声明,例如printfscanf。周围的角括号stdio.h表明这一点stdio.h可以使用搜索策略找到,该策略更喜欢编译器与其他名称相同的标头提供的标题,而不是通常包括本地或项目特定的标头文件的双引号。

下一行表示一个名称的函数main正在定义。这main功能在C程序中具有特殊目的;运行时环境调用main功能开始执行程序。类型说明符int指示返回调用者的值(在这种情况下为运行时环境),结果评估main功能,是一个整数。关键字void作为参数列表,表明此功能没有参数。

开头的卷发括号表示定义的开始main功能。

下一行调用(将执行转移到)一个名为的函数printf,在这种情况下是从系统提供的。在这个电话中,printf函数将传递(提供)一个参数,即字符串字符字符中的第一个字符的地址"hello, world\n"。字符串文字是一个未命名的数组,带有类型的元素char,由编译器自动设置具有最终null (ASCII值0)字符的编译器以标记数组的末端(用于printf要知道字符串的长度)。零字符也可以写为逃生序列,写为\0。这\n是C转换为newline字符的逃生序列,该字符表示输出表示当前线的末端。返回值printf功能是类型int,但是由于不使用它而被静静地丢弃。 (一个更仔细的程序可能会测试返回值以确定是否是否printf功能成功。)半龙;终止语句。

闭合的卷曲支架指示了代码的结尾main功能。根据C99规格和新的规格main功能与任何其他功能不同,将隐式返回一个值0到达}终止功能。 (以前是明确的return 0;需要语句。)这是由运行时系统解释为指示成功执行的退出代码。

数据类型

C中的类型系统静态的且弱键入的,这使其类似于Algol后代(例如Pascal)的类型系统。有各种尺寸的整数内置类型,包括签名和未签名,浮点数和枚举类型(enum)。整数类型char通常用於单字节字符。 C99添加了布尔数据类型。也有派生类型,包括数组指针记录struct)和工会union).

C通常用于低级系统编程中,其中可能需要从类型系统中逃脱。编译器试图确保键入大多数表达式的正确性,但是程序员可以通过使用类型的铸件将值从一种类型明确转换为另一种类型,或者使用Pointer或Unions来重新诠释基础位,以各种方式覆盖支票以某种方式的数据对象。

有些人发现C的声明语法不直觉,尤其是用于功能指针。 (Ritchie的想法是在上下文中声明标识符类似于其用途:“声明反映了使用”。)

C通常的算术转换允许生成有效的代码,但有时会产生意外的结果。例如,比较具有相等宽度的签名和未签名的整数需要将签名值转换为未签名。如果签名值为负,则可能会产生意外的结果。

指针

C支持使用指针,这是一种记录对像或功能在内存中的地址或位置的参考。可以指出指针可以访问存储在指向地址的数据,也可以调用尖锐的功能。可以使用分配或指针算术来操纵指针。指针值的运行时表示通常是一个原始内存地址(也许是由偏移字段增强),但是由于指针的类型包括指向的内容的类型,因此可以通过类型检查在内的表达式。在编译时。指针算术会自动按指向数据类型的大小缩放。

指针用于C中的许多用途。文本字符串通常使用指针将其操作到字符阵列中。动态内存分配是使用指针进行的;一个结果malloc通常会施放到要存储的数据的数据类型。许多数据类型(例如)通常被动态分配struct使用指针将对象链接在一起。其他指针的指针经常用于多维阵列和阵列struct对象。对功能的指针(函数指针)对于将函数作为参数传递给高阶功能(例如QSortbsearch ),在调度表中或对事件处理程序的回调很有用。

空指针明确指向没有有效的位置。删除无效指针值是未定义的,通常会导致分割故障。 NULL指针值可用于指示特殊情况,例如链接列表的最终节点中的“ Next”指针,或作为返回指针函数的错误指示。在源代码中的适当上下文中,例如为指针变量分配,可以将null指针常数写为0,有或没有明确施放指针类型,NULL宏由几个标准标头定义,或者,因为C23具有常数nullptr。在条件上下文中,无效指针值评估为false,而所有其他指针值则评估为true。

无效指针(void *)指向未指定类型的对象,因此可以用作“通用”数据指针。由于尚不清楚指向对象的大小和类型,因此不能删除指针,也不能允许使用指针算术,尽管它们很容易被(在许多情况下隐含地)转换为其他任何对象指针,但它们很容易被转换为(许多情况下)。类型。

粗心使用指针可能是危险的。由于通常不受组织选中,因此可以使指针变量指向任何任意位置,这可能会导致不良效果。尽管正确使用的指针指向安全的地方,但可以通过使用无效的指针算术来指向不安全的地方。他们指出的对象可能会在交易后继续使用(悬挂的指针);它们可以在没有初始化的情况下使用(野生指针);或者,可以使用演员,联合或其他损坏的指针直接将它们直接分配给不安全的值。通常,C可以允许操纵指针类型之间的转换,尽管编译器通常提供各种检查级别的选项。其他一些编程语言通过使用更多限制性参考类型来解决这些问题。

数组

C中的数组类型传统上是在编译时指定的固定静态大小。最近的C99标准还允许一种可变长度阵列的形式。但是,也可以在运行时使用标准库的运行时间分配(任意大小)的内存(任意大小)malloc功能,并将其视为阵列。

由于始终通过指针访问数组(实际上),因此通常针对基础数组大小检查数组访问,尽管某些编译器可能会提供界限作为选项。因此,违反数组范围是可能的,并且可能导致各种影响,包括非法内存访问,数据损坏,缓冲区超支和运行时间异常。

C没有针对声明多维数组的特殊规定,而是依赖于类型系统中的递归来声明数组的数组,从而有效地完成了同一件事。可以将结果“多维阵列”的索引值视为在行序列上的增加。多维阵列通常用于数值算法(主要来自应用线性代数)以存储矩阵。 C数组的结构非常适合此特定任务。但是,在早期版本中,必须知道数组的边界固定值,否则将明确传递给需要它们的任何子例程,并且动态大小的数组数组无法使用双索引访问。 (这样做的解决方法是将指针的附加“行矢量”分配给列。)C99引入了解决此问题的“可变长度阵列”。

使用现代C(C99或更高版本)的以下示例显示了堆上二维数组的分配以及用于访问的多维数组索引的使用(可以在许多C编译器上使用界限检查):

int func(int N, int M)
{
  float (*p)[N][M] = malloc(sizeof *p);
  if (p == 0)
    return -1;
  for (int i = 0; i < N; i++)
    for (int j = 0; j < M; j++)
      (*p)[i][j] = i + j;
  print_array(N, M, p);
  free(p);
  return 1;
}

这是使用C99的自动VLA功能的类似实现:

int func(int N, int M)
{
  // Caution: checks should be made to ensure N*M*sizeof(float) does NOT exceed limitations for auto VLAs and is within available size of stack.
  float p[N][M]; // auto VLA is held on the stack, and sized when the function is invoked
  for (int i = 0; i < N; i++)
    for (int j = 0; j < M; j++)
      p[i][j] = i + j;
  print_array(N, M, p);
  // no need to free(p) since it will disappear when the function exits, along with the rest of the stack frame
  return 1;
}

阵列 - 货架互换性

下标表示x[i](在哪里x指定指针)是句法糖*(x+i)。利用编译器对指针类型的了解,地址x + i指向不是基础地址(指向x)递增i字节,而是将其定义为基本地址增加i乘以元素的大小x指着。因此,x[i]指定i+1数组的元素。

此外,在大多数表达上下文中(值得注意的例外是sizeof),将数组类型的表达式自动转换为指针转换为数组的第一个元素。这意味着,当数组命名为函数的参数时,数组从未被复制为整体,而是传递其第一个元素的地址。因此,尽管C中的函数调用使用逐个价值语义,但阵列仍会通过参考生效。

数组的总尺寸x可以通过申请确定sizeof到阵列类型的表达。可以通过应用操作员来确定元素的大小sizeof到阵列的任何删除元素A,如n = sizeof A[0]。因此,声明的数组中的元素数量A可以确定为sizeof A / sizeof A[0]。请注意,如果仅由于上述自动转换而在C代码中通常可以使用第一个元素的指针,则有关数组及其长度的全部类型的信息丢失。

内存管理

编程语言的最重要功能之一是提供管理内存和存储在内存中的对象的设施。 C提供了三种分配对象内存的主要方法:

  • 静态内存分配:对象的空间在编译时在二进制中提供;只要将包含它们的二进制装入内存,这些对象就有一定程度(或寿命)。
  • 自动内存分配:临时对象可以存储在堆栈上,并且在退出块后,该空间将自动释放和重复使用。
  • 动态内存分配:可以使用库函数(例如malloc从记忆区域称为;这些块一直持续存在,直到随后通过调用库函数释放以重复使用realloc或者free

这三种方法在不同的情况下是适当的,并且具有各种权衡。例如,静态内存分配几乎没有分配开销,自动分配可能涉及更多的开销,而动态内存分配可能在分配和交易中都有很大的开销。静态对象的持久性质对于跨函数呼叫维护状态信息很有用,自动分配易于使用,但堆栈空间通常比静态内存或堆积空间更有限和瞬时,并且动态内存分配允许其方便的对象分配其对象的分配尺寸仅在运行时才知道。大多数C程序都大量使用这三个程序。

在可能的情况下,自动或静态分配通常最简单,因为存储器由编译器管理,从而使程序员释放了可能会出现错误的手动分配和释放存储的繁琐的琐事。但是,许多数据结构在运行时的大小可能变化,并且由于静态分配(以及C99之前的自动分配)必须在编译时具有固定的尺寸,因此在许多情况下需要动态分配。在制定C99标准之前,可变大小的阵列就是一个常见的示例。 (请参阅有关的文章malloc对于动态分配的数组的示例。)与自动分配不同的是,在运行时间可能会失败,而后果不受控制,当无法分配所需的存储时,动态分配函数返回指示(以空指针值的形式)。 (在程序甚至可以开始执行之前,通常由链接器加载程序检测到太大的静态分配。)

除非另有说明,否则静态对像在程序启动时包含零或空指针值。仅当明确指定初始值时,才会自动和动态分配的对像初始化;否则,它们最初具有不确定的值(通常,存储中存在的任何位模式都存在,甚至可能不代表该类型的有效值)。如果程序试图访问非初始化的值,则结果是未定义的。许多现代编译器试图检测并警告这个问题,但是可能会出现误报和假否定性

堆内存分配必须与其在任何程序中的实际用法同步,以尽可能重复使用。例如,如果唯一的指向堆内存分配的指针超出了范围或在明确交易之前具有其值覆盖的值,则该内存无法恢复以备以后重复使用,并且本质上丢失了该程序,这是一种被称为内存的现象泄露相反,可以释放内存,但随后被引用,导致无法预测的结果。通常,失败症状出现在与导致错误的代码无关的程序的一部分中,因此很难诊断失败。这些问题用自动垃圾收集的语言改善。

C编程语言使用作为其主要扩展方法。在C中,库是一个“存档”文件中包含的一组函数。每个库通常都有一个标题文件,其中包含程序中包含的功能的原型,以及这些功能使用的特殊数据类型和宏观符号的声明。为了使程序使用库,它必须包括库的标题文件,并且必须将库与该程序链接,在许多情况下,该程序需要编译器标志(例如,例如,-lm,用于“链接数学库”的速记)。

最常见的C库是C标准库,该库由ISOANSI C标准指定,并带有每个C实现(针对有限环境(例如嵌入式系统)的实现可能仅提供标准库的子集)。该库支持流输入和输出,内存分配,数学,字符字符串和时间值。几个单独的标准标头(例如,stdio.h)指定这些和其他标准库设施的接口。

另一组C库函数是专门针对UNIXUNIX样系统的应用程序使用的功能,尤其是为内核提供接口的功能。这些功能以各种标准详细介绍,例如POSIX单个UNIX规范

由于许多程序已在C中编写,因此还有其他各种各样的图书馆。库通常是在C中编写的,因为C编译器生成有效的对象代码。然后,程序员创建库的接口,以便可以从JavaPerlPython等高级语言中使用例程。

文件处理和流

文件输入和输出(I/O)不是C语言本身的一部分,而是由库(例如C标准库)及其关联的标头文件处理的(例如stdio.h)。通常通过流通过来工作的高级I/O实现文件处理。从这个角度来看,流是独立于设备的数据流,而文件是混凝土设备。高级I/O是通过流与文件的关联来完成的。在C标准库中,缓冲区(存储区域或队列)暂时用于存储数据,然后将其发送到最终目的地。这减少了等待较慢的设备所花费的时间,例如硬盘驱动器固态驱动器。低级I/O功能不是标准C库的一部分,而是“裸机”编程的一部分(编程与任何操作系统(例如大多数嵌入式编程)无关)。除少数例外,实现包括低级I/O。

语言工具

已经开发了许多工具来帮助C程序员以不确定的行为或可能错误的表达方式查找和修复语句,并且比编译器提供的更严格。该工具是第一个这样的工具,导致了许多其他工具。

自动化源代码检查和审核在任何语言上都是有益的,对于C,存在许多此类工具,例如棉绒。一种常见的做法是使用皮棉在第一次编写程序时检测可疑代码。一旦程序通过棉布,然后使用C编译器对其进行编译。同样,许多编译器可以选择警告句法有效的结构,这些结构实际上可能是错误。 Misra C是一套专有的指南,旨在避免为嵌入式系统开发的这种可疑代码。

还有用于执行不是C标准部分的操作的编译器,库和操作系统级别的机制,例如检查数组的界限,缓冲区溢出的检测,序列化动态内存跟踪和自动垃圾收集

诸如纯化valgrind的工具以及与包含内存分配功能的特殊版本的库链接可以帮助发现内存使用时的运行时错误。

用途

用于系统编程的基本原理

一些用C编写的软件

C广泛用于实现操作系统嵌入式系统应用程序的系统编程。这是出于几个原因:

  • C语言允许使用指示器和类型的PENNING访问平台硬件和内存,因此可以将系统特定的功能(例如控制/状态寄存器I/O寄存器)配置并与C中编写的代码一起使用 - 它允许对它正在运行的平台。
  • 编译后生成的代码不需要许多系统功能,并且可以直接从某些引导代码调用 - 执行易于执行。
  • C语言语句和表达式通常可以很好地映射到目标处理器的指令序列,因此,系统资源的运行时需求较低 - 执行速度很快。
  • 凭借其丰富的操作员,C语言可以利用目标CPU的许多功能。如果特定的CPU具有更深奥的说明,则可以使用固有功能来构建语言变体来利用这些说明 - 它几乎可以使用所有目标CPU的功能。
  • 该语言使叠加结构易于在二进制数据的块上叠加,从而可以理解,导航和修改数据 - 它可以编写数据结构,甚至可以编写文件系统。
  • 该语言为整数算术和逻辑提供了丰富的操作员,包括位操作人员,以及浮点数的大小不同 - 它可以有效地处理适当的结构数据。
  • C是一种相当小的语言,只有少数语句,并且没有太多的功能产生广泛的目标代码 - 这是可以理解的。
  • C可以直接控制内存分配和交易,从而为内存处理操作提供了合理的效率和可预测的时机,而无需担心零星的停止 - 世界垃圾收集事件 - 它具有可预测的性能。
  • C允许使用和实施不同的内存分配方案,包括典型mallocfree;具有竞技场的更复杂的机制;或可能适合DMA ,使用中断处理程序或与虚拟内存系统集成的OS内核版本。
  • 根据链接器和环境,C代码还可以调用用汇编语言编写的库,并且可以从汇编语言中调用 - 它与其他低级代码互联网良好。
  • C及其呼叫约定和接头结构通常与其他高级语言结合使用,并呼叫C与C受支持的C呼叫 - 它与其他高级代码相互互动。
  • C具有非常成熟且广泛的生态系统,包括图书馆,框架,开源编译器,辩论者和公用事业,并且是事实上的标准。驱动程序可能已经存在于C中,或者有类似的CPU体系结构与C编译器的后端相似,因此选择其他语言的动机减少了。

曾经用于网络开发

从历史上看,C有时使用通用网关接口(CGI)作为“网关”进行Web开发,以在Web应用程序,服务器和浏览器之间进行信息。 C可能是由于其速度,稳定性和近乎全世界的可用性而不是解释的语言选择的。网络开发在C中不再是常见的实践,并且存在许多其他Web开发工具

其他一些语言本身是用C编写的

C的广泛可用性效率结果是, C编译器口译员通常在C中实现。

用于计算密集型库

C使程序员能够创建算法和数据结构的有效实现,因为硬件的抽象层是薄的,并且其开销很低,这是计算密集型程序的重要标准。例如, GNU多个精度算术库GNU科学库MathematicaMatlab是完全或部分编写的C。许多语言支持在C中调用库函数,例如,基于Python的框架Numpy使用C用于高的c for高的c for高 - 绩效和硬件相互作用方面。

C作为中间语言

C有时通过实现其他语言将C用作中间语言。这种方法可用于可移植性或便利性;通过使用C作为中间语言,无需其他机器特定的代码生成器。 C具有某些功能,例如在初始化器列表末尾的线数预处理器指令和可选的多余逗号,这些逗号支持生成的代码的汇编。但是,C的某些缺点促使开发了其他专门用于中间语言(例如C-)的基于C的语言。同样,当代主要编译器GCCLLVM都具有中间表示,不是C,这些编译器支持包括C在内的许多语言的前端。

最终用户应用程序

C还广泛用于实施最终用户应用程序。但是,此类应用程序也可以用更新的高级语言编写。

限制

汇编语言的力量和...汇编语言的便利性

-丹尼斯·里奇(Dennis Ritchie)

尽管C一直受欢迎,有影响力和巨大成功,但它具有缺点,包括:

  • 标准的动态内存处理mallocfree是错误的。错误包括:内存分配但未释放时内存泄漏;并访问以前释放的内存。
  • 使用指针和直接操纵内存意味着可能导致记忆的损坏,这可能是由于程序员错误或对不良数据的检查不足所致。
  • 有某种类型的检查,但不适用于诸如variadic功能之类的区域,并且可以琐碎或无意间进行检查。它是弱输入的
  • 由于编译器生成的代码本身几乎包含检查,因此程序员有一个负担来考虑所有可能的结果,以防止缓冲区超支,数组界限检查,堆栈溢出,内存耗尽,并考虑种族条件,线程隔离等。
  • 使用指示器和对这些指示的运行时间操作意味着可以有两种访问相同数据(混叠)的方法,在编译时不能确定这些数据。这意味着C中无法使用其他语言的一些优化。Fortran被认为更快。
  • 某些标准库功能,例如scanf或者strncat,可能导致缓冲区超支
  • 在生成代码中对低级变体的支持的标准化有限,例如:不同的函数调用约定ABI ;不同的结构包装约定;较大整数中的不同字节订购(包括伊尼亚语)。在许多语言实现中,这些选项中的一些可以使用预处理器指令来处理#pragma,有些具有其他关键字,例如使用__cdecl召集惯例。但是,指令和选项并不一致。
  • 使用标准库处理的字符串处理是密集型的,需要明确的内存管理。
  • 该语言不直接支持对象取向,内省,运行时表达式评估,仿制药等。
  • 很少有防护能力反对不适当使用语言功能,这可能导致无与伦比的代码。特别是, C预处理器可以隐藏令人不安的效果,例如双重评估和更糟。诸如国际混淆的C代码竞赛《卑鄙的C竞赛》等比赛庆祝了这一棘手代码的设施。
  • C缺乏对异常处理的标准支持,仅提供错误检查的返回代码。这setjmplongjmp标准库函数已用于通过宏实现一个尝试捕获机制。

出于某些目的,已经采用了限制的c,例如misra ccert c ,以减少虫子的机会。诸如CWE之类的数据库试图计算C等的方式以及缓解建议。

有一些工具可以减轻某些缺点。当代C编译器包括可能会产生警告以帮助识别许多潜在错误的检查。

其中一些缺点促使其他语言的构建。

相关语言

TIOBE索引图,显示了各种编程语言的普及的比较

C直接和间接影响了许多以后的语言,例如C ++Java 。最普遍的影响是句法。提到的所有语言都结合了C的语句,(或多或少可识别) C的表达语法与类型系统,数据模型或与C不同的大规模程序结构,有时是从根本上讲。

存在几个C或近C口译员,包括CHCINT ,也可以用于脚本。

面向对象的编程语言变得流行时, C ++Objective-C是C提供面向对像功能的两个不同的C。两种语言最初都是作为源代码编译器实施的。源代码转换为C,然后使用C编译器进行编译。

C ++编程语言(最初命名为“ with Class ”)是由Bjarne Stroustrup设计的,它是一种使用类似C的语法提供面向对象功能的方法。 C ++添加了更大的键入强度,范围和其他在面向对象的编程中有用的工具,并允许通过模板进行通用编程。现在几乎是C,C ++的超级集团支持C的大部分C,但少数例外

Objective-C最初是C顶上非常“薄”的层,并且仍然是C的严格超集C,该c允许使用混合动态/静态/静态键入范式使用contiments的编程。 Objective-C从C和SmallTalk的语法中得出其语法:涉及预处理,表达式,函数声明和函数调用的语法是从C继承的,而面向对象特征的语法最初是从SmallTalk中获取的。

除了C ++Objective-CCHCILK统一的平行C外,C是C的超级集。

也可以看看