C语言
《C程序设计语言》(英语:The C Programming Language,有时也称为K&R) | |
编程范型 | 程序式指令式编程(过程式)、结构化编程 |
---|---|
設計者 | 丹尼斯·里奇(Dennis Ritchie) |
實作者 | 丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson) |
发行时间 | 1972年 |
穩定版本 | ISO/IEC 9899:2011(C11) ( 2011年11月 ) |
作業系統 | 跨平台 |
主要實作產品 | |
Clang、GCC、MSVC、Turbo C、Watcom C | |
啟發語言 | |
B(BCPL、CPL)、ALGOL 68[1]、組合語言、PL/I、FORTRAN | |
影響語言 | |
大量, 如:awk、BitC、csh、C++、C#、 D、Java、JavaScript、Objective-C、Perl、PHP、Rust等 | |
|
C是一种通用的程式語言,广泛用于系统软件与应用软件的开发。于1969年至1973年間,為了移植與開發UNIX作業系統,由丹尼斯·里奇與肯·汤普逊,以B语言为基础,在贝尔实验室設計、开发出來。
C语言具有高效、灵活、功能丰富、表达力强和較高的可移植性等特点,在程式設計中备受青睐,成为最近25年使用最为广泛的编程语言[2]。目前,C语言編譯器普遍存在於各種不同的操作系统中,例如Microsoft Windows、macOS、Linux、Unix等。C語言的設計影響了众多後來的程式語言,例如C++、Objective-C、Java、C#等。
二十世纪八十年代,為了避免各開發廠商用的C語言語法產生差異,由美國國家標準局為C語言訂定了一套完整的國際標準語法,稱為ANSI C,作為C語言的標準。二十世纪八十年代至今的有关程式開發工具,一般都支持符合ANSI C的語法。
目录
1 設計
2 概述
2.1 特性
3 歷史
3.1 早期發展
3.2 K&R C
3.3 ANSI C 和 ISO C
3.4 C99
3.5 C11
4 語法
4.1 Hello World 程序
4.2 进一步了解
4.3 复合语句
4.4 條件語句
4.5 循環語句
4.6 跳轉語句
4.7 在C語言中的运算符号
4.8 安全性
4.9 数据类型
4.9.1 基础数据类型
4.9.2 结构数据类型
4.10 数组
4.10.1 指针
4.10.2 字串
4.11 文件輸入/輸出
4.11.1 標準輸入/輸出
4.12 函数
5 内存管理
6 安全问题
7 库
8 工具軟體
9 保留关键字
9.1 C99新增关键字
9.2 C11新增关键字
10 經典錯誤
11 参见
12 参考
12.1 注解
13 外部連結
設計
C語言設計目標是提供一種能以簡易的方式編譯、處理低階記憶體、產生少量的機械碼以及不需要任何執行環境支援便能執行的程式語言。C語言也很適合搭配汇编语言來使用。儘管C語言提供許多低階處理的功能,但仍保持良好跨平台的特性,以一個標準規格寫出的C語言程式可在許多電腦平台上進行編譯,甚至包含一些嵌入式处理器(微控制器或称MCU)以及超級電腦等作業平台。
概述
特性
- C語言是一個有結構化程式設計、具有变量作用域(variable scope)以及遞迴功能的程序式語言。
- C語言傳遞參數均是以值傳遞(pass by value)[3],另外也可以傳遞指针(a pointer passed by value)。
- 不同的變數類型可以用結構体(struct)組合在一起。
- 只有32個保留字(reserved keywords),使变量、函數命名有更多彈性。
- 部份的变量類型可以轉換,例如整型和字符型变量。
- 透過指標(pointer),C語言可以容易的對記憶體進行低階控制。
- 编译预处理(preprocessor)讓C語言的編譯更具有彈性。
歷史
早期發展
C語言最早由丹尼斯·里奇(Dennis Ritchie)為了在PDP-11電腦上運行的Unix系統所設計出來的程式語言,第一次發展在1969年到1973年之間。
C源于BCPL语言,后者由馬丁·理察德(Martin Richards)于1967年左右设计实现。BCPL是一门"无类型"的编程语言:它仅能操作一种数据类型,即机器字(machine word)。1970年,肯·汤普逊为运行在PDP-7上的首个Unix系统设计了一个精简版的BCPL,这个语言被称为B语言,它也是无类型的。
Unix最早運行在PDP-7上,是以組合語言寫成。在PDP-11出现後,丹尼斯·里奇與肯·汤普逊着手將Unix移植到PDP-11上,无类型的语言在PDP-11上愈发显得合适。PDP-11提供了多种不同规格大小的基本对象:一字节长的字符,两字节长的整型数以及四字节长的浮点数。B语言无法处理这些不同规格大小的对象,也没有提供单独的操作符去操作它们。
C语言最初尝试通过向B语言中增加数据类型的想法来处理那些不同类型的数据。和大多数语言一样,在C中,每个对象都有一个类型以及一个值;类型决定了可用于值的操作的含义,以及对象占用的存储空间大小。
1973年,Unix作業系統的核心正式用C語言改寫,這是C語言第一次應用在作業系統的核心編寫上。
1975年C语言开始移植到其他机器上使用。史蒂芬·強生实现了一套「可移植编译器」,这套编译器修改起来相对容易,并且可以为不同的机器生成代码。从那时起,C在大多数计算机上被使用,从最小的微型计算机到与CRAY-2超级计算机。C语言很规范,即使没有一份正式的标准,你也可以写出C程序,这些程序无须修改就可以运行在任何支持C语言和最小运行时环境的计算机上。
C最初在小型机器上实现,并且继承了一系列小语种编程语言的特点;与功能相比,C的设计者更倾向于简单和优雅。此外,从一开始,C语言就是为系统级编程而设计,程序的运行效率至关重要,因此,C语言与真实机器能力的良好匹配也就不足为奇。例如,C语言为典型硬件所直接支持的对象:字符,整数(也许有多种大小),以及浮点数(同样可能有多种大小)提供了相应的基本数据类型。
K&R C
1978年,丹尼斯·里奇和布萊恩·柯林漢合作出版了《C程序设计语言》的第一版。書中介紹的C語言標準也被C語言程式設計師稱作“K&R C”,第二版的書中也包含了一些ANSI C的標準。
K&R C主要引入了以下语言特性:
- 标准I/O库
- 结构(
struct
)类型 - 长整数(
long int
)类型 - 无符号整数(
unsigned int
)类型 - 把运算符
=+
和=-
改为+=
和-=
。因为=+
和=-
會使得編譯器不知道使用者要處理i = -10
還是i =- 10
,使得處理上產生混淆。
即使在後來ANSI C標準被提出的許多年後,K&R C仍然是許多編譯器的最低標準要求,許多老舊的編譯仍然運行K&R C的標準。
ANSI C 和 ISO C
1989年,C语言被美國國家標準協會(ANSI)标准化,編號為ANSI X3.159-1989。這個版本又稱為C89。标准化的一个目的是扩展K&R C,增加了一些新特性。
void 函数- 函数返回 struct 或 union 类型
void * 数据类型
1990年,国际标准化组织(ISO)成立 ISO/IEC JTC1/SC22/WG14 工作组,来规定国际标准的C语言,通过对ANSI标准的少量修改,最终製定了 ISO 9899:1990,又稱為C90。随后,ANSI亦接受國際標準C,並不再發展新的C標準。
K&R C语言到ANSI/ISO标准C语言的改进包括:
- 增加了真正的标准库
- 新的预处理命令与特性
- 函数原型允许在函数申明中指定参数类型
- 一些新的关键字,包括 const、volatile 与 signed
- 宽字符、宽字符串与多字节字符
- 对约定规则、声明和类型检查的许多小改动与澄清
WG14工作小组之後又於1994年,对1985年颁布的标准做了两处技术修订(缺陷修复)和一个补充(扩展)。下面是 1994 年做出的所有修改:
- 3 个新的标准库头文件 iso646.h、wctype.h 和 wchar.h
- 几个新的记号与预定义宏,用于对国际化提供更好的支持
- printf/sprintf 函数一系列新的格式代码
- 大量的函数和一些类型与常量,用于多字节字符和宽字节字符
C99
在ANSI的标准确立後,C语言的规范在一段时间内没有大的变动,然而C++在自己的标准化建立过程中继续发展壮大。《标准修正案一》在1994年为C语言建立了一个新标准,但是只修正了一些C89标准中的细节和增加更多更广的国际字符集支持。不过,这个标准引出了1999年ISO 9899:1999的发表。它通常被称为C99。C99被ANSI于2000年3月采用。
在C99中包括的特性有:
- 增加了对编译器的限制,比如源始碼每行要求至少支持到 4095 字节,变量名函数名的要求支持到 63 字节(extern 要求支持到 31)。
- 增强了预处理功能。例如:
巨集支持取可变参数 #define Macro(...) __VA_ARGS__- 使用巨集的时候,允许省略参数,被省略的参数会被扩展成空串。
- 支持 // 开头的单行注释(这个特性实际上在C89的很多编译器上已经被支持了)
- 增加了新关键字 restrict, inline, _Complex, _Imaginary, _Bool
- 支持 long long, long double _Complex, float _Complex 等类型
- 支持不定长的数组,即数组长度可以在运行时决定,比如利用变量作为数组长度。声明时使用 int a[var] 的形式。不过考虑到效率和实现,不定长数组不能用在全局,或 struct 与 union 。
变量声明不必放在语句块的开头,for 语句提倡写成 for(int i=0;i<100;++i) 的形式,即i 只在 for 语句块内部有效。- 允许采用(type_name)xx,xx,xx 类似于 C++ 的构造函数的形式构造匿名的结构体。
- 初始化结构的时候允许对特定的元素赋值,形式为:
struct testint a[3],b; foo = [0].a = 1, [1].a = 2 ;
struct testint a, b, c, d; foo = .a = 1, .c = 3, 4, .b = 5 ; // 3,4 是对 .c,.d 赋值的
- 格式化字符串中,利用 u 支持 unicode 的字符。
- 支持 16 进制的浮点数的描述。
- printf scanf 的格式化串增加了对 long long int 类型的支持。
- 浮点数的内部数据描述支持了新标准,可以使用 #pragma 编译器指令指定。
- 除了已有的 __line__ __file__ 以外,增加了 __func__ 得到当前的函数名。
- 允许编译器化简非常数的表达式。
- 修改了 / % 处理负数时的定义,这样可以给出明确的结果,例如在C89中-22 / 7 = -3, -22 % 7 = -1,也可以-22 / 7= -4, -22 % 7 = 6。 而C99中明确为 -22 / 7 = -3, -22 % 7 = -1,只有一种结果。
- 取消了函数返回类型默认为 int 的规定。
- 允许在 struct 的最后定义的数组不指定其长度,写做 (flexible array member)。
const const int i 将被当作 const int i 处理。- 增加和修改了一些标准头文件,比如定义 bool 的 <stdbool.h> ,定义一些标准长度的 int 的 <inttypes.h> ,定义复数的 <complex.h> ,定义宽字符的 <wctype.h> ,类似于泛型的数学函数 <tgmath.h>, 浮点数相关的 <fenv.h>。 在<stdarg.h> 增加了 va_copy 用于复制 ... 的参数。<time.h> 里增加了 struct tmx ,对 struct tm 做了扩展。
- 输入输出对宽字符以及长整数等做了相应的支持。
但是各个公司对C99的支持所表现出来的兴趣不同。当GCC和其它一些商业编译器支持C99的大部分特性的时候[4],微软和Borland却似乎对此不感兴趣。
C11
2011年12月8日,ISO正式发布了新的C语言的新标准C11,之前被称为C1X,官方名称为ISO/IEC 9899:2011。新的标准提高了对C++的兼容性,并增加了一些新的特性。这些新特性包括泛型巨集、多執行緒、帶邊界檢查的函式、匿名結構等。但直到現在仍未有編譯器完整的支援。
語法
Hello World 程序
下面是一個在標準輸出設備(stdout)上打印出 "Hello, world!" 字串的簡單程式。類似的程式,通常作為初學程式語言時的第一個程式:
1 #include <stdio.h>
2
3 int main(void)
4
5 printf("Hello, world!n");
6 return 0;
7
进一步了解
C语言由函数和变量组成,C的函数就像是Fortran中的子程序和函数。
在C语言中,程序从 main
开始执行。main
函数通过调用和控制其他函数进行工作。例如上面的printf
。程序员可以自己写函数,或从库中调用函数。在上面的return 0;
使得 main
返回一个值给调用程序的壳层,表明程序是否成功运行。
一个C语言的函数由返回值、函数名、参数列表和函数体组成。函数体的语法和其它的复合的语句部分是一样的。
复合语句
C语言中的复合语句(或称语句块)的格式为:
语句;
语句;
/* ... */
复合语句可以使得几个语句从文法上变成一个语句。
有时必须使用复合语句,否则会产生错误。例如,在运用循环语句的时候,如果循环体(即循环中执行部分)包含多个语句(以分号隔开),则必须使用花括号将他们合并成一个复合语句。如果不这么做,系统仅把第一个分号前的内容看做循环体。
需要注意的是,部分C编译器并不支持在任意位置使用复合语句。
條件語句
C語言有两種條件語句形式,分别是if和switch。
If 的格式如下:
if(運算式) // 如果
語句;
// 有时还会有 else:
else // 否则
語句;
運算式的值非零表示條件為真;如果條件为假,程式將跳過if处的語句,直接執行if後面的語句。但是如果if後面有else,則當條件为假時,程式跳到else處執行。if和else後面的語句可以是另個if語句,這種套疊式的結構,允許更複雜的邏輯控制流程得以實現。在一般情況下,else一定與最接近的if成對,因此常用括弧越過此限制。比較下面兩種情況:
if(逻辑表达式)
if (逻辑表达式)
語句;
else
語句;
1 if(逻辑表达式甲)
2
3 if(逻辑表达式乙)
4 語句;
5
6 else
7 語句;
要注意这里的缩进和换行只用于方便阅读。编译器并不会根据缩进层级猜测 if 和 else 的对应关系。
switch通常用於對幾種有明確值的條件進行控制。它要求的條件值通常是整數或字元。與switch搭配的條件轉移是case。使用case後面的標值,控制程式將跳到滿足條件的case處一直往下執行,直到語句結束或遇到break。通常可以使用default把其他例外的情況包含進去。如果switch語句中的條件不成立,控制程式將跳到default處執行;如果省略default子句,則直接執行下一語句。switch是可以嵌套的。
switch(值)
case 甲:
case 乙:
語句段1; // 甲乙都会执行
// 更多语句…
break; // 跳转到 switch 末尾处
case 丙:
語句段2;
break;
default: // 无论如何都会匹配
語句段3;
簡單的條件判斷也可用?:
運算式?值1:值2;
如:
a=b>c?b:c // 如果變數"b"的值大於變數"c" 把變數"b"的值賦予變數"a"
循環語句
C語言有三種形式的循環語句:
do
語句
while(判断式);
和:
while(判断式)
語句;
和:
for(起始化; 判断式;運算式)
語句;
在while和for中,語句將執行到表達式的值為零時结束。在do...while語句中,循環將至少被執行一次。这三種循環結構可以互相轉化:
for(起始化; 判断式;運算式)
語句;
如果語句中不使用continue語句的话,相當於
初始化;
while (判断式)
語句;
運算式;
當循環條件一直為真時,將產生無窮迴圈。
跳轉語句
跳轉語句包括四種:goto,continue,break和return。
goto 標記;
goto語句是無條件轉移語句,且標記必須在當前函數中定義,使用“標記:”的格式定義。程式將跳到標記處繼續執行。由於goto(特别是向回 goto 和长距离的 goto)容易產生閱讀上的困難,所以对新手應該儘量少用。GCC 编译器拓展支持对指针 goto和宏内 goto,一定程度上增强了 goto 的可读性。
continue語句用在迴圈語句中,作用是結束當前一輪的迴圈,馬上開始下一輪迴圈。
break語句用在迴圈語句或switch中,作用是結束當前迴圈,跳到循環體外繼續執行。但是使用break只能跳出一層迴圈。在要跳出多重迴圈時,可以使用goto使得程式更為簡潔。
當一個函數執行結束後要返回一個值時,使用return。return可以跟一個運算式或變數。如果return後面沒有值,將執行不返回值。
在C語言中的运算符号
()、 、 -> 、 .、 !、 ++、 -- | 圆括号、方括号、指標、成員、逻辑非、自加、自减 |
++ 、 -- 、 * 、 & 、 ~ 、 ! 、 + 、 - 、 sizeof、(cast) | 单目运算符 |
* 、 / 、 %、== | 算术运算符 |
+ 、 - | 算术运算符 |
<< 、 >> | 位运算符 |
< 、 <= 、 > 、 >= | 关系运算符 |
== 、 != | 关系运算符号 |
& | 位与 |
^ | 位异或 |
| | 位或 |
&& | 逻辑与 |
|| | 逻辑或 |
? : | 条件运算符 |
= 、 += 、 -= 、 *= 、 /= 、 %= 、 &= 、 |= 、 ^= | 赋值运算符 |
, | 顺序运算符 |
比較特別的是,位元右移(>>)運算子可以是算術(左端補最高有效位)或是邏輯(左端補 0)位移。例如,將 11100011 右移 3 位元,算術右移後成為 11111100,邏輯右移則為 00011100。因算術位元右移較適於處理帶負號整數,所以幾乎所有的編譯器都是算術位元右移。[5]
运算符的优先级从高到低大致是:单目运算符、算术运算符、关系运算符、逻辑运算符、条件运算符、赋值运算符(=)和逗号运算符。
安全性
符號 | 安全性 | 符號 | 安全性 | 符號 | 安全性 | 符號 | 安全性 |
---|---|---|---|---|---|---|---|
+ | 溢位,包裹,循環 | -= | 溢位,包裹,循環,截裁 | >> | 無 | >= | 無 |
- | 溢位,包裹,循環 | *= | 溢位,包裹,循環,截裁 | & | 無 | == | 無 |
* | 溢位,包裹,循環 | /= | 溢位,截裁 | ~ | 無 | != | 無 |
% | 溢位 | <<= | 溢位,包裹,循環,截裁 | ! | 無 | && | 無 |
++ | >>= | 截裁 | un+ | 無 | || | 無 | |
-- | &= | 截裁 | un- | 溢位,包裹,截裁 | ?: | 無 | |
= | |= | 截裁 | < | 無 | |||
+= | << | 溢位,包裹,截裁 | > | 無 |
数据类型
基础数据类型
注意:以下是典型的数据位长和范围。编译器可能使用不同的数据位长和范围。请参考具体的参考手册。
在标准头文件limits.h 和 float.h中说明了基础数据的长度。float,double和long double的范围就是在IEEE 754标准中提及的典型数据。
关键字 | 位长(字节) | 范围 | 格式化字符串 |
---|---|---|---|
char | 1 bytes | -128..127(或0..255,与体系结构相关) | %c |
unsigned char | 1bytes | 0..255 | %c, %hhu |
signed char | 1bytes | -128..127 | %c, %hhd, %hhi |
int | 2bytes(16位系统) 或 4bytes | -32768..32767 或 -2147483648..2147483647 | %i, %d |
unsigned int | 2bytes 或 4 bytes | 0..65535 或 0..4294967295 | %u |
signed int | 2bytes 或 4bytes | -32768..32767 或 -2147483648..2147483647 | %i, %d |
short int | 2bytes | -32768..32767 | %hi, %hd |
unsigned short | 2 bytes | 0..65535 | %hu |
signed short | 2bytes | -32768..32767 | %hi, %hd |
long int | 4bytes 或 8bytes[6] | -2147483648..2147483647 或 -9223372036854775808..9223372036854775807 | %li, %ld |
unsigned long | 4bytes 或 8 bytes | 0..4294967295 或 0..18446744073709551615 | %lu |
signed long | 4 bytes或 8bytes | -2147483648..2147483647 或 -9223372036854775808..9223372036854775807 | %li, %ld |
long long | 8bytes | -9223372036854775808..9223372036854775807 | %lli, %lld |
unsigned long long | 8bytes | 0..18446744073709551615 | %llu |
float | 4bytes | 2.939x10−38..3.403x10+38 (7 sf) | %f, %e, %g |
double | 8bytes | 5.563x10−309..1.798x10+308 (15 sf) | %lf, %e, %g |
long double | 10bytes或 16bytes | 7.065x10-9865..1.415x109864 (18 sf或33 sf) | %Lf, %Le, %Lg |
结构数据类型
结构数据类型允许构造由多个基础数据类型组合而成的复杂结构,结构数据类型为面向对象的蓝本。以下的结构数据类型通过指针实现了二叉树结构:
1 typedef struct Bintree
2 int data;
3 struct bintree *lchild; // left child of the node
4 struct bintree *rchild; // right child of the node
5 bintree; // 自定义 bintree 类型
为结构数据类型定义变量时通常会用到动态内存分配:
1 #define mktree() (bintree *)malloc(sizeof(bintree)) // 分配该结构所需的内存单元数量
2 bintree *tree;
3 tree = mktree(); // 分配到 tree 指针
4 tree->data = 1;
5 tree->lchild = mktree();
6 ...
由于C语言不具备自动垃圾收集(Garbage Collection)功能,使用完毕后调用free(treePtr)
来释放之前通过malloc(size)
分配的内存。详见以下指针章节。
数组
如果一個變數名後面跟著一個有數字的中括弧,這個聲明就是陣列聲明。字串也是一種陣列,它們以ASCII的NUL作為陣列的結束。要特別注意的是,方括內的索引值是從0算起的。
例如:
int myvector [100];/* 從myvector[0]至[99]共100個元素 */
char mystring [80];
// 声明时初始化
float mymatrix [3] [2] = 2.0 , 10.0, 20.0, 123.0, 1.0, 1.0;
int notfull [3][3] = 1,1,2,3,4,5;
// 数组套数组
char lexicon [10000] [300];/* 共一萬個最大長度為300的字元陣列。*/
int a[3][4];
上面最後一個例子創建了一個陣列,但也可以把它看成是一個多維陣列。注意陣列的下標從0開始。這個陣列的結構如下:
a[0][0] | a[0][1] | a[0][2] | a[0][3] |
a[1][0] | a[1][1] | a[1][2] | a[1][3] |
a[2][0] | a[2][1] | a[2][2] | a[2][3] |
例子中notfull創建了一個3*3的二維陣列,初始化時有些元素並未賦值。如下:
1 ? ?
1 2 3
4 5 ?
根据C标准的规定,在存在初始化列表时,如果初始化列表中未提供对所有元素的初始化,则剩余元素会被默认初始化,并使用与静态变量相同的初始化规则。[7]
指针
如果一个变量声明时在前面使用 * 号,表明这是个指针型变量。换句话说,该变量存储一个地址,而 *(此处特指单目运算符 *,下同。C语言中另有双目运算符 * 表示乘) 则是取内容操作符,意思是取这个内存地址里存储的内容。把这两点结合在一起,可将 int *a;
看作是 “*a 解得的内容类型为 int”,对更复杂的声明也如此[註 1]。指针是 C 语言区别于其他同时代高级语言的主要特征之一。
指针不仅可以是变量的地址,还可以是数组、数组元素、函数的地址。通过指针作为形式参数可以在函数的调用过程得到一个以上的返回值(不同于return z
这样的仅能得到一个返回值。
指针是一把双刃剑,许多操作可以通过指针自然的表达,但是不正确的或者过分的使用指针又会给程序带来大量潜在的错误。
例如:
int *pi; // 指向整型数据的指针 pi
int * api[3];// 由指向整型数据的指针构成的数组,长度为 3
char ** argv;// 指向一个字符指针的指针
struct int member; stinst,
* pst = & stinst;
// pst是一个指向一个匿名结构体的指针
储存在指针中的地址所指向的数值在程序中可以由 * 读取。例如,在第一个例子中, *pi 是一个整型数据。这叫做引用一个指针。
另一个运算符 &,叫做取地址运算符,它将返回一个变量、数组或函数的存储地址。因此,下面的例子:
int i, *pi; /* int and pointer to int */
pi = &i;
i 和 *pi 在程序中可以相互替换使用,直到 pi 被改变成指向另一个变量的地址。
当指针指向结构体时,可以使用运算符 -> 代替 *和. 的作用,如 (*p).m 与 p->m 等效。
字串
C语言的字符串其实就是char型数组,所以使用字符串並不需要引用庫。然而C標準庫確實包含了用於對字符串進行操作的函數,使得它們看起來就像字符串而不是陣列。使用這些函數需要引用標頭檔string.h。
文件輸入/輸出
在C語言中,輸入和輸出是經由標準函式庫中的一組函數來實現的。在ANSI/ISO C中,這些函數被定義在標頭檔stdio.h中。
標準輸入/輸出
有三個標準輸入/輸出是标准I/O库預先定義的:
- stdin 標準輸入
- stdout 標準輸出
- stderr 輸入輸出錯誤
下面的這個例子顯示了一個過濾程式(filter program)是怎樣構成的。
#include <stdio.h>
int main(int argc, const char * argv)
char c;
while ((c=getchar())!=EOF)
putchar(c);
perror("getchar() got EOF");
return -1;
函数
C语言的基本结构单位是函数。系统首先调用 main函数(主函数),通过函数的嵌套调用,再调用其他函数。函数可以是系统自带的函数,也可以是用户定义的函数。C语言中,不允许函数嵌套定义。
内存管理
C语言的特色之一是:程序员必须亲自处理内存的分配细节。
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標準中:
文件 | 简介说明 |
---|---|
<assert.h> | 断言相关 |
<ctype.h> | 字符类型判断 |
<errno.h> | 标准报错机制 |
<float.h> | 浮点运算 |
<limits.h> | 各种体系结构限制 |
<locale.h> | 本地化接口 |
<math.h> | 数学函数 |
<setjmp.h> | 跨函数跳转 |
<signal.h> | 信号(类似UNIX的信号定义,但是差很远) |
<stdarg.h> | 可变参处理 |
<stddef.h> | 一些标准宏定义 |
<stdio.h> | 标准I/O库 |
<stdlib.h> | 标准工具库函数 |
<string.h> | ASCIIZ字符串及任意内存处理函数 |
<time.h> | 时间相关 |
在94年的修正版中
- <iso646.h>
- <wchar.h>
- <wctype.h>
在C99中增加了六個函式庫
- <complex.h>
- <fenv.h>
- <inttypes.h>
- <stdbool.h>
- <stdint.h>
- <tgmath.h>
以上是C语言的标准。各个系统各自又对C库函数进行的各种扩充,就浩如烟海了。如POSIX C、GNU C等。
工具軟體
工具軟體可以幫助程式設計者避免一些程式中潛藏或容易出現的問題,例如常會造成程式未預期動作或是執行期錯誤的程式碼。
許多語言都有自動源代碼檢查及審計工具,C語言也有類似工具,像是Lint。可以在程式剛寫好時用Lint找出可能有問題的程式,通過Lint後再用C編譯器進行編譯,許多編譯器也可以設定是否要針對一些可能有問題的程式碼提出警告。MISRA C是一套針對嵌入式系統的法則,可主要也是避免一些可能有問題的程式碼。
也有一些編譯器、程式庫或作業系統可以處理一些非標準C語言的功能,例如邊界值檢查、缓存溢出偵測、序列化及自動垃圾回收功能。
使用像Valgrind或IBM Rational Purify等軟體工具,或者連結有特別malloc函式的程式庫,有助於找出一些運行期記憶體使用的問題。
保留关键字
以下是C语言的保留关键字:[8]
char | short | int | unsigned |
long | float | double | struct |
union | void | enum | signed |
const | volatile | typedef | auto |
register | static | extern | break |
case | continue | default | do |
else | for | goto | if |
return | switch | while | sizeof |
C99新增关键字
_Bool | _Complex | _Imaginary | inline | restrict |
C11新增关键字
_Alignas | _Alignof | _Atomic | _Generic | _Noreturn |
_Static_assert | _Thread_local |
經典錯誤
“void main()”的用法并不是任何標准制定的[9][10]。 C語言标准語法是“int main()”,任何实现都必须支持int main(void) /* ... */
和int main(int argc, char* argv) /* ... */
[11]。 在 C++ 標準中,main的標準型態應是int,否则类型是由实现定义的。任何实现都必须支持int main() /* ... */
和int main(int argc, char* argv) /* ... */
[12]。
参见
- 块 (C语言扩展)
参考
^ Ritchie, Dennis M. The Development of the C Language. January 1993 [1 January 2008].The scheme of type composition adopted by C owes considerable debt to Algol 68, although it did not, perhaps, emerge in a form that Algol's adherents would approve of.
^ TIOBE Programming Community Index [TIOBE编程社区指数]. 2012 [2012-11-03] (英语).
^ Brian W. Kernighan and Dennis M. Ritchie. The C programming Language. Prentice-Hall. 1988. ISBN 0-13-110362-8 (英语).In C, all function arguments are passed ``by value.
^ GCC从3.0版本开始正式支持C99(-std=c99)
^ 经测试,gcc4.4.3使用C99标准的结果依然是00011100,即逻辑移位。
^ GCC、Clang 等实现中,64位代码的long类型为64位,而MSVC中则维持32位
^ [http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf WG14 N1570 Committee Draft — April 12, 2011] 6.7.9 Initialization 21 If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.
^ ISO/IEC 9899:201x (ISO C11) Committee Draft
^ Can I write "void main()"?The definitionvoid main() /* ... */
is not and never has been C++, nor has it even been C.
^ 用 C99 进行开放源代码的开发
^ 「The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:int main(void) /* ... */
or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):int main(int argc, char *argv) /* ... */
or equivalent; or in some other implementation-defined manner.」,引自ISO/IEC 9899:1999, Section 5.1.2.2.1 Program startup
^ 「An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a return type of type int, but otherwise its type is implementation-defined. All implementations shall allow both of the following definitions of main:int main() /* ... */
andint main(int argc, char* argv) /* ... */
.」,引自 ISO/IEC 14882, 第一版(1998)、第二版(2003)與第三版(2011), section 3.6.1 Main function
Brian Kernighan, Dennis Ritchie: The C Programming Language. Also known as K&R — The original book on C.- 1st, Prentice Hall 1978; ISBN 978-0-13-110163-0. Pre-ANSI C.
- 2nd, Prentice Hall 1988; ISBN 978-0-13-110362-7. ANSI C.
ISO/IEC 9899. The official C:1999 standard, along with technical corrigenda and a rationale. As of 2005 the latest version is ISO/IEC 9899:TC2.
Samuel P. Harbison, Guy L. Steele: C: A Reference Manual. This book is excellent as a definitive reference manual, and for those working on C compilers. The book contains a BNF grammar for C.- 4th, Prentice Hall 1994; ISBN 978-0-13-326224-7.
- 5th, Prentice Hall 2002; ISBN 978-0-13-089592-9.
Derek M. Jones: The New C Standard: A Cultural and Economic Commentary, Addison-Wesley, ISBN 978-0-201-70917-9, online material
Robert Sedgewick: Algorithms in C, Addison-Wesley, ISBN 978-0-201-31452-6 (Part 1–4) and ISBN 978-0-201-31663-6 (Part 5)- William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery: Numerical Recipes in C (The Art of Scientific Computing), ISBN 978-0-521-43108-8
注解
^ C 的声明使用这种“解方程”的形式,于是便出现了多种复杂的声明。https://cdecl.org 是一个基于网页的实用“翻译工具”。
外部連結
维基共享资源中相关的多媒体资源:C语言 |
Coding Programmer Page / C Library Reference and Examples (english)- GCC 首页
- GLIBC2 首页
- Visual Studio Express 首頁
ISO/IEC 9899。C99标准的官方网站。目前(2009年)可直接下载的标准文本是 ISO/IEC 9899:201x。
|
|
|
|