C语言 技术专题简介

简介

  “C程序设计语言”重定向至此。关于书籍,请见“C程序设计语言 (书)”。C语言《C程序设计语言》,第一部介绍C语言的书籍编程范型过程式指令式编程(过程式)、结构化编程设计者丹尼斯·里奇(Dennis Ritchie)实现者丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson)发行时间1972年操作系统跨平台主要实现产品Clang、GCC、MSVC、Turbo C、Watcom C启发语言B(BCPL、CPL)、ALGOL 68、汇编语言、PL/I、FORTRAN影响语言大量,如:awk、BitC(英语:BitC)、csh、C++、C#、D、Java、JavaScript、Objective-C、Perl、PHP、Rust等 教科书中有关C Programming的文本C是一种通用的、过程式编程编程语言,支持结构化编程、词法作用域和递归,使用静态类型系统,并且广泛用于系统软件与应用软件的开发。C语言于1969年至1973年间,为了移植与开发UNIX操作系统,由丹尼斯·里奇与肯·汤普逊,以B语言为基础,在贝尔实验室设计、开发出来。二十世纪八十年代,C语言应用日渐广泛。为了避免各开发厂商用的C语言的语法产生差异,美国国家标准局为C语言订定了一套完整的国际标准语法,称为ANSI C,作为C语言的标准。与此同时,国际标准化组织也接受该标准为国际标准。因此,ANSI C也同时被称为ISO C。二十世纪八十年代至今的有关程序开发工具,一般都支持符合ANSI C的语法。C语言具有高效、灵活、功能丰富、表达力强和较高的可移植性等特点,在程序设计中备受青睐,成为最近25年使用最为广泛的编程语言。目前,C语言编译器普遍存在于各种不同的操作系统中,例如Microsoft Windows、macOS、Linux、Unix等。C语言的设计影响了众多后来的编程语言,例如C++、Objective-C、Java、C#等。现行的许多软件都是由C语言或者其影响和派生的编程语言开发出来的。

概述

与ALGOL一族的大多数过程式编程语言类似,C语言是一个有结构化程序设计、具有变量作用域(variable scope)以及递归功能的过程式语言。其采用的静态类型系统可以防止无意的程序设计操作。C语言中所有的可执行代码都被包含在子程序(函数)里。其传递参数均是以值传递(pass by value),另外也可以传递指针(a pointer passed by value)。C语言是自由形式语言,即其源代码的缩进并不影响程序的功能,而是使用分号作为语句的结尾,花括号来表示代码块。

由于C语言的语言规模较小,若干高层的机制需要使用定义的函数来提供。比如,C语言并没有直接处理复合对象(例如字符串、集合、列表、数组等)的操作,也没有对于存储器分配工具和内存回收工具的直接定义,同时也本身不具有输入和输出以及文件访问的方法。然而,用户定义的函数和C语言标准库中的函数为这些高层的机制提供了可能性。

C语言也具有以下的特性:

基本数据类型包括字符、整型和浮点数等。另外也有派生的各种数据类型,如指针、数组、结构和联合。

部分的变量类型可以转换,例如整数型和字符型变量。

透过指针(pointer),C语言可以容易的对存储器进行低端控制。

不同的变量类型可以用结构体(struct)组合在一起。

具有基本的控制流:语句组、条件判断、多路选择、循环等。

函数可以返回各种数据类型的值,并且都可以递归调用。每次调用函数会重新创建变量。

C语言只有32个保留字(reserved keywords),使变量、函数命名有更多弹性。

编译预处理(preprocessor)让C语言的编译更具有弹性。

历史

主条目:C语言的历史

20世纪70年代,肯·汤姆森为了使其设计的Unix系统更加高效,使用B语言的变种(即C语言)在DEC PDP-7计算机上重写了Unix。C语言中许多重要概念来源于BCPL语言,其对C语言的影响也间接地来源于B语言。在1978年,丹尼斯·里奇和布莱恩·柯林汉合作出版了《C程序设计语言》第一版,事实上即为K&R C标准。1983年,为了制定一个独立于具体机器且无歧义的C语言标准,美国国家标准协会成立了一个委员会,并在1988年完成了该标准的制定,即ANSI C。此标准同时被国际标准化组织所采纳,也被称作ISO C。

其后,C语言至今经历了几次标准更新,诞生了C99、C11和目前最新的标准C18。C语言标准的下一次更新C2x目前正在起草中。

语法

主条目:C语言语法此章节需要扩充

C语言的形式文法由国际标准化组织所制定。C语言的源代码包含声明和函数定义。函数定义又包含声明和语句。声明可以定义一个新的类型,比如结构(struct)、联合(union)和枚举(enum),也可以为变量赋予类型和预留内存。复合语句由一对花括号包含,可以使得几个语句从文法上变成一个语句。

作为一种指令式编程语言,C语言使用语句执行操作。最常见的语句是表达式语句,由一个表达式后加一个分号组成,可以令系统调用函数和为变量赋值。C语言也提供各种用保留关键字组成的控制流语句,以达到结构化编程的效果,例如选择语句(ifelse)、循环语句(for循环、while循环和dowhile循环)和跳转语句(gotobreakcontinue)。表达式可以包含不同的运算符,也可以调用函数。

Hello World 程序

主条目:Hello World Brian Kernighan于1978年亲笔书写的“Hello World”程序.mw-parser-output .quotebox{background-color:#F9F9F9;border:1px solid #aaa;box-sizing:border-box;padding:.25em .5em;font-size:88%;max-width:100%}.mw-parser-output .quotebox.floatleft{margin:.5em 1.4em .8em 0}.mw-parser-output .quotebox.floatright{margin:.5em 0 .8em 1.4em}.mw-parser-output .quotebox.centered{margin:.5em auto .8em auto}.mw-parser-output .quotebox p{font-style:inherit;margin-bottom:0}.mw-parser-output .quotebox-title{background-color:#F9F9F9;text-align:center;font-size:larger;font-weight:bold;margin-bottom:0}.mw-parser-output .quotebox-quote.quoted:before{font-family:”Times New Roman”,serif;font-weight:bold;font-size:large;color:gray;content:” “ “;vertical-align:-45%;line-height:0}.mw-parser-output .quotebox-quote.quoted:after{font-family:”Times New Roman”,serif;font-weight:bold;font-size:large;color:gray;content:” ” “;line-height:0}.mw-parser-output .quotebox .left-aligned{text-align:left}.mw-parser-output .quotebox .right-aligned{text-align:right}.mw-parser-output .quotebox .center-aligned{text-align:center}.mw-parser-output .quotebox cite{display:block;font-style:normal}@media all and (max-width:720px){.mw-parser-output .quotebox{width:100%!important;margin:0 0 .8em!important;float:none!important}}”对于所有语言的初学者来说,编写的第一个程序几乎都是相同的,即‘请打印出下列内容 hello, world’”– 《C程序设计语言》

现在广泛被编程初学者使用的”hello, world”程序实例最初就是出现在《C程序设计语言》第一版中。下面是一个在标准输出设备(stdout)上打印出 “Hello, world!” 字符串的简单程序。类似的程序,通常作为初学编程语言时的第一个程序:

#include int main(void) {    printf("Hello, world!n");    return 0;}

其中只有int,void,return为C语言的关键字,预处理器会将#include 替换为stdio.h文件的内容。

main函数是C语言程序的入口点。

"Hello, world!n"中的n是一个转义字符,形式为加上一个字符。所起的作用在ASCII码中规定。

printf是声明于stdio.h的函数,关于printf的更多细节,参见printf;

关于格式化字符串的更多信息,参见格式化字符串。

内存管理

C语言的特色之一是:程序员必须亲自处理内存的分配细节。语言不负责内存边界检查,这是因为在运行时进行内存边界检查会造成性能问题,与UNIX哲学不符。此特性容易导致缓冲区溢出问题。然而,部分编译器(如英特尔编译器)会出于安全性的考量,提供方法以进行运行时内存边界检查。

大多数C语言实现使用栈(Stack)来保存函数返回地址/栈帧基址、完成函数的参数传递和函数局部变量的存储。然而,在部分极特殊的平台上,使用栈并不能获得最大效率。此时的实现由编译器决定。如果程序需要在运行的过程中动态分配内存,可以利用堆(Heap)来实现。

基本上C程序的元素存储在内存的时候有3种分配策略:

静态分配

如果一个变量声明为全局变量或者是函数的静态变量,这个变量的存储将使用静态分配方式。静态分配的内存一般会被编译器放在数据段或代码段来存储,具体取决于实现。这样做的前提是,在编译时就必须确定变量的大小。以IA32的x86平台及gcc编译器为例,全局及静态变量放在数据段的低端;全局及静态常量放在代码段的高端。

自动分配

函数的自动局部变量应该随着函数的返回会自动释放(失效),这个要求在一般的体系中都是利用栈(Stack)来满足的。相比于静态分配,这时候,就不必绝对要求这个变量在编译时就必须确定变量的大小,运行时才决定也不迟,但是C89仍然要求在编译时就要确定,而C99放松了这个限制。但无论是C89还是C99,都不允许一个已经分配的自动变量运行时改变大小。

所以说C函数永远不应该返回一个局部变量的地址

要指出的是,自动分配也属于动态分配,甚至可以用alloca函数来像分配堆(Heap)一样进行分配,而且释放是自动的。

动态分配

还有一种更加特殊的情况,变量的大小在运行时有可能改变,或者虽然单个变量大小不变,变量的数目却有很大弹性,不能静态分配或者自动分配,这时候可以使用堆(Heap)来满足要求。ANSI C定义的堆操作函数是malloc、calloc、realloc和free。

使用堆(Heap)内存将带来额外的开销和风险。

主条目:C 标准库

C语言的标准文档要求了一个平台移植C语言的时候至少要实现的一些功能和封装的集合,称为“标准库”,标准库的声明头部通过预处理器命令#include进行引用。

在C89标准中:

文件 简介说明

断言相关字符类型判断标准报错机制浮点运算各种体系结构限制本地化接口数学函数跨函数跳转信号(类似UNIX的信号定义,但是差很远)可变参处理一些标准宏定义标准I/O库标准工具库函数ASCII字符串及任意内存处理函数时间相关

在94年的修正版中

在C99中增加了六个库

以上是C语言的标准。各个系统各自又对C库函数进行的各种扩充,就浩如烟海了。如POSIX C、GNU C等。

工具软件

工具软件可以帮助程序设计者避免一些程序中潜藏或容易出现的问题,例如常会造成程序未预期动作或是执行期错误的代码。

许多语言都有自动源代码检查及审计工具,C语言也有类似工具,像是Lint。可以在程序刚写好时用Lint找出可能有问题的程序,通过Lint后再用C编译器进行编译,许多编译器也可以设置是否要针对一些可能有问题的代码提出警告。MISRA C是一套针对嵌入式系统的法则,可主要也是避免一些可能有问题的代码。

也有一些编译器、程序库或操作系统可以处理一些非标准C语言的功能,例如边界值检查、缓存溢出侦测、序列化及自动垃圾回收功能。

使用像Valgrind或IBM Rational Purify(英语:Purify)等软件工具,或者链接有特别malloc函数的程序库,有助于找出一些运行期存储器使用的问题。

经典错误

“void main()”的用法并不是任何标准制定的。 C语言标准语法是“int main()”,任何实现都必须支持int main(void) { /* ... */ }int main(int argc, char* argv) { /* ... */ }。 在 C++ 标准中,main的标准类型应是int,否则类型是由实现定义的。任何实现都必须支持int main() { /* ... */ }int main(int argc, char* argv) { /* ... */ }

Published by

风君子

独自遨游何稽首 揭天掀地慰生平