深入理解 Linux C 标准程序设计

栏目: C · 发布时间: 5年前

内容简介:ANSI C 是由MinGW(

ANSI C 是由 美国国家标准协会(ANSI)国际标准化组织(ISO) 共同发布的 C 编程语言标准,自 1989 年首次发布以来经历了 C89、C90、C95、C99、C11 一系列版本演进,目前最新的版本是 2018 年 10 月发布的 C18 标准。由于嵌入式开发中广泛使用的 GCC 提供了对 C89 至 C11 一系列标准特性的完整支持,因此本文选用了包含 GCC 二进包的 MinGW 作为 C 程序编译工具。

深入理解 Linux C 标准程序设计

MinGW( Minimalist GNU for Windows )是一个可以运行在 Windows 操作系统的 GNU 开源 工具 链,主要包含有 GNU Compiler Collection( GCC )、The GNU Binary Utilities( GNU binutils )、 GNU Debugger( GDB )、以及一系列特定于 Windows 平台的头文件与静态链接库。最初的 mingw32 项目已经于 2013 年停止开发,现由另一位作者的 MinGW-w64 项目替代。

Hello Wrold

main 函数是 C 语言程序的执行入口,因此也称为 主函数 。主函数的 argc 参数为整型,用于统计程序执行时传递给主函数的命令行参数的个数。而 argv 参数是一个字符串数组,用于存放指向字符串参数的指针数组,数组中每个元素指向一个命令行输入的参数。不同于 Python 、JavaScript 这样的脚本语言,函数内部每条语句尾部的分号 ; 都不能被省略。代码开头的 #include 预处理指令用于包含标准 IO 头文件,从而能够在后续主函数中调用 printf() 方法。另外值得注意的是,C 语言代码当中存在 /*块注释*///行注释 两种注释风格,开发人员可以根据需要酌情使用。

#include <stdio.h>

/*
  块注释
*/
int main(int argc, char *argv[]) {
    printf("hello world!");      // 行注释
    return 0;
}

C99 标准规定主函数执行完成之后,需要显式书写 return 0; 语句表示程序正常退出,主函数返回类型的声明也需要显式的设置为 int

变量与常量

变量是一个具有名称的存储单元,编译系统会自动为 变量名 分配对应的内存地址。 C 程序中的变量都是数据的补码形式进行存储 ,程序运行时计算机会通过 变量名 查找对应的内存单元地址,然后通过该地址操作其中保存的 变量值

深入理解 Linux C 标准程序设计

下面代码当中,声明了一个整型变量 date ,并将其赋值为 2019

int date = 2019;

常量在程序运行期间不允许修改其值,C99 规范允许使用 const 关键字声明一个常量,下面将声明一个常量 USER 并赋值为 Hank通常约定常量名称全部大写 )。

const int USER = "Hank";

C 语言是强类型语言,ANSI C 当中无论定义变量还是常量都需要事先声明数据类型,编译器将会根据数据类型来为变量和常量分配相应存储空间,不同数据类型具有不同的 存储长度存储方式 ,C99 标准中常用的数据类型见下表:

深入理解 Linux C 标准程序设计

注意:红色部分表示 C99 标准新增的特性。

整型 int

整型数据会以整数补码的方式存储,Keil C51 编译器会分配 2 个字节共 16 位空间,而 GCC 编译器则会分配 4 个字节共 32 位空间。32 位当中最左边的一位是 符号位 ,该位为 0 表示正数,为 1 则表示负数。接下来的表格展示了整型数据的存储空间以及取值范围:

数据类型 字节数 取值范围
int 基本整型 2 个字节 -32768 ~ 32767 ,即$-2^{15} \Rightarrow (2^{15}-1)$。
- 4 个字节 -2147483648 ~ 2147483647 ,即$-2^{31} \Rightarrow (2^{31}-1)$。
unsigned int 无符号基本整型 2 个字节 0 ~ 65535 ,即$0 \Rightarrow (2^{16}-1)$。
- 4 个字节 0 ~ 4294967295 ,即$0 \Rightarrow (2^{32}-1)$。
short 短整型 2 个字节 -32768 ~ 32767 ,即$-2^{15} \Rightarrow (2^{15}-1)$。
unsigned short 无符号短整型 2 个字节 0 ~ 65535 ,即$0 \Rightarrow (2^{16}-1)$。
long 长整型 4 个字节 -2147483648 ~ 2147483648 ,即$-2^{31} \Rightarrow (2^{31}-1)$。
unsigned long 无符号长整型 4 个字节 0 ~ 4294967295 ,即$0 \Rightarrow (2^{32}-1)$。
long long 双长整型 8 个字节 -9223372036854775808 ~ 9223372036854775807 ,即$-2^{63} \Rightarrow (2^{63}-1)$。
unsigned long long 无符号双长整型 8 个字节 0 ~ 18446744073709551615 ,即$0 \Rightarrow (2^{64}-1)$。

注意上面表格当中,无符号类型由于需要有 1 位来作为符号位,因此取值范围计算公式的 指数部分 需要相应的减去 1 位(例如:$2^{15}$、$2^{31}$、$-2^{63}$),而有符号类型计算公式的指数部分则与该数据类型可存储的字节数相匹配。另外,取值范围计算公式$2^{n}-1$中出现的减去 1 的情况,是由于正整数一侧的取值范围包含了 0 (*虽然数学上 0 并非正整数_),因而需要将正整数部分的取值范围相应的减掉一。

为了更加清晰的理解整型数据存储空间分配与取值范围的关系,下面的示意图展示了 基本整型 的最大取值 32767无符号基本整型 的最大取值 65535 各自的位存储空间占用情况:

深入理解 Linux C 标准程序设计

sizeof() 并非一个函数调用,而是 C 语言提供的一个单目操作符;其作用是返回当前操作数所占用存储空间的 字节 大小。

#include <stdio.h>

int main() {
    printf("int %d byte\n", sizeof(int));                                // int 4 byte
    printf("unsigned int %d byte\n", sizeof(unsigned int));              // unsigned int 4 byte
    printf("short %d byte\n", sizeof(short));                            // short 2 byte
    printf("unsigned short %d byte\n", sizeof(unsigned short));          // unsigned short 2 byte
    printf("long %d byte\n", sizeof(long));                              // long 4 byte
    printf("unsigned %d byte\n", sizeof(unsigned));                      // unsigned 4 byte
    printf("long long %d byte\n", sizeof(long long));                    // long long 8 byte
    printf("unsigned long long %d byte\n", sizeof(unsigned long long));  // unsigned long long 8 byte
    return 0;
}

如果需要使用 printf() 输出中文,源代码文件必须以 GB2312 编码格式保存。

值得提醒的是,基本整型 int 默认是有符号的数据类型,而无符号类型变量原则上不能存放 -3 这样的负数;使用 printf() 输出无符号整型数据的时候,格式字符串需要选择 %u 进行输出。

#include <stdio.h>

int main() {
    int birthday= -1988;
    printf("%d\n", birthday);  // -1988
    printf("%u\n", birthday);  // 4294965308
    return 0;
}

字符型 char

C 语言当中的字符型数据必须以单引号 'c' 声明,每个字符型变量只能保存 1 个 ASCII 有效字符,这是由于字符类型实际存储的是该字符的 ASCII 编码,因为 ASCII 字符集编码通常表达为一个整型数据,所以 C99 规范当中也将其视为一种整型数据。下面的表格展示了字符型占用的存储空间以及取值范围:

数据类型 字节数 取值范围
signed char 有符号字符型 1 个字节 -128 ~ 127 ,即$-2^{7} \Rightarrow (2^{7}-1)$。
unsigned char 无符号字符型 1 个字节 0 ~ 255 ,即$0 \Rightarrow (2^{8}-1)$。

有符号字符型数据允许存储的取值范围在 -128 ~ 127 之间,但字符型的 ASCII 编码不可能为负值,因而实际只会使用到 0 ~ 127 ,即最左侧符号位总是为 0 。如果将负整数直接赋值给字符型变量,操作虽然合法但并不代表一个有效字符,而仅仅保存了一个负整数值。接下来的图片展示了保存字符型变量 '1' 时的存储情况,由于字符 '1' 的 ASCII 码为 49 ,因此存储器中实质保存的是数字 49 的二进制表达形式。

深入理解 Linux C 标准程序设计

printf() 输出字符型数据时,格式字符串需要选择 %c ;如果格式字符串选择为 %d ,则会输入该变量的 ASCII 码表达形式。

#include <stdio.h>

int main() {
    char test= 'h';
    printf("%d\n", test);  // 104
    printf("%c\n", test);  // h
    return 0;
}

需要注意,关键字 char 前的 signed 或者 unsigned 是否能够缺省由具体的编译器决定,这一点与 int 等其它数据类型不同。在 MinGW 提供的 GCC 编译器当中, char 缺省为 有符号类型

#include <stdio.h>

int main() {
    char test = 255;
    printf("%c \n", test);  // 
    printf("%d \n", test);  // -1
    return 0;
}

由于 char 默认为有符号类型,所以赋值为 255 超出了有符号字符类型的表示范围,导致后面打印输出为 -1 。如果这里显式声明字符型 test 的值为无符号类型 unsigned 则能够正确的打印数值 255

浮点型 float

浮点型用来表示具有小数点的 实数 ,并以规范化的二进制数指数形式存放在存储单元。之所以称为浮点型,是由于实数的指数形式有多种,比如对于 3.1416 ,可以表示为$3.14159 × 10^0$、$0.314159 × 10^1$、$0.0314159 × 10^2$等形式,小数点的位置可以自由进行浮动。

数据类型 字节数 有效数字 取值范围(绝对值)
float 单精度浮点型 4 个字节 6 $0$ 以及 $(1.2×10^{-38}) \Rightarrow (3.4×10^{38})$。
double 双精度浮点型 8 个字节 15 $0$ 以及 $(2.3×10^{-308}) \Rightarrow (1.7×10^{308})$。
long double 长双精度浮点型 8 个字节 15 $0$ 以及 $(2.3×10^{-308}) \Rightarrow (1.7×10^{308})$。
- 16 个字节 19 $0$ 以及 $(3.4×10^{-4932}) \Rightarrow (1.1×10^{4932})$。

为了保持存储结构的一致性,必须将实数转换为 规范化的指数形式 后再保存至存储单元,即小数点前数字为 0 ,小数点之后第 1 位数字不为 0 ,对应于前面例子 3.1416 的规范化的指数形式是$0.314159 × 10^1$,下图展示了其具体的存储结构,注意小数部分 .314159 实际是以二进制形式保存在存储单元中的。

深入理解 Linux C 标准程序设计

C99 标准并未明确定义浮点类型的指数和小数部分各占据总存储单元的多少,具体数值由各个编译器指定。同时,各个编译器对于浮点数据类型所占用的存储空间长度也有所不同,例如 long double 类型的长度在 MinGW 的 GCC 中被定义为 12 个字节。

#include <stdio.h>

int main() {
    float pi_f = 3.14;
    double pi_b = 3.14;
    long double pi_ld = 3.14;

    printf("float %d byte\n", sizeof(pi_f));         // float 4 byte
    printf("double int %d byte\n", sizeof(pi_b));    // double int 8 byte
    printf("long double %d byte\n", sizeof(pi_ld));  // long double 12 byte

    return 0;
}

需要注意,C 语言对于常量值都会按照基本数据类型进行处理。

#include <stdio.h>

int main() {
    float i  = 3.14159;

    printf("float is %d byte\n", sizeof(i));        // float is 4 byte
    printf("double is %d byte\n", sizeof(3.14159)); // double is 8 byte

    return 0;
}

基本数据类型转换

自动类型转换

两个不同的基本数据类型进行算术运算时,结果总是存储占用空间更大的那个数据类型。比如下面例子中,整型的 i (占用 4 字节存储空间)与字符类型的 c (占用 1 字节存储空间)分别与浮点类型 f 相加(占用 4 字节存储空间)时,得到的 sizeof 结果总是 4 个字节。

#include <stdio.h>

int main() {
    int i  = 5;
    float f = 3.14;
    char c = 'H';

    printf("result have %d byte\n", sizeof(i+f)); // result have 4 byte
    printf("result have %d byte\n", sizeof(c+f)); // result have 4 byte

    return 0;
}

强制类型转换

可以利用强制类型转换将数据转换为需要的数据类型,使用格式为 (目标数据类型)表达式 ,例如: (double)1(double)(3+5) 都会将整型的结果转换为双精度浮点类型。

#include <stdio.h>

int main() {
    char i  = 'A';
    printf("result have %d byte\n", sizeof((float)i)); // result still have 4 byte
    return 0;
}

数组 array

数组是一组有序数据的集合,数组中每一个元素都是相同的数据类型,并且保存在 一个连续的存储空间 。C 语言中使用数组必须先声明其长度,以便于事先开辟一个指定大小的存储空间。例如:下图展示了一个包含有 10 个元素的数组 int a[10] ,该数组每个元素的存储空间只能用于存放整型数据。

深入理解 Linux C 标准程序设计

结构体 struct

由于数组只能存放相同数据类型的数据,而结构体是一种由 不同类型数据 组成的组合型数据结构。使用时需要先声明结构体类型,再建立结构体变量。下面的 C 语言代码定义了一个 Data 结构体和 Student 结构体:

/* 声明Date结构体类型 */
struct Date {
  int year;
  int month;
  int day;
}

/* 声明Student结构体类型 */
struct Student {
  int age;
  char name[10];
  char address[20];
  struct Date birthday; // 声明Date结构体变量
}

struct Student hank; // 声明Student结构体变量

定义结构体变量以后,系统会对其分配内存单元。结构体变量占用的内存长度是各数据类型成员所占用的长度之和,每个成员都拥有自己独立的内存单元。例如对于上面代码中定义的 DataStudent 结构体,声明并定义之后的内存布局如下图所示:

深入理解 Linux C 标准程序设计

共用体 union

共用体可以在同一个地址开始的内存单元中存放几种不同的数据类型,正是由于占用内存的起始地址相同,所以 共用体某一时刻只能存放一个数据,而不能同时存放多个 。下面代码中,我们来声明一个叫做 Variable 的结构体变量,该变量具备存放字符型、整型、浮点型数据的能力。

union Variable {
  char character;  // 有符号字符型,占用1个字节空间。
  int integer;     // 基本整型,占用2个字节空间。
  float real;      // 单精度浮点型,占用4个字节空间。
}

Variable.integer = 100; // 将整数100存储在Variable结构体变量。

共用体变量所占用的内存长度,等于内存占用最长的那个数据类型的变量长度。因此,上面定义的 Variable 结构体变量占用的存储空间为 4 个字节,即占用内存空间最大的浮点型数据 real 的长度。

深入理解 Linux C 标准程序设计


以上所述就是小编给大家介绍的《深入理解 Linux C 标准程序设计》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

松本行弘的程式世界

松本行弘的程式世界

松本行弘 / 鄧瑋敦 / 博碩 / 2010年07月27日

讓Ruby之父教您大師級的程式思考術! 本書以松本行弘先生對程式本質的深層認知、各種技術之優缺點的掌握,闡述Ruby這套程式語言的設計理念,並由此延伸讓您一窺程式設計的奧妙之處。本書內含許多以Ruby、Lisp、Smalltalk、Erlang、JavaScript等動態語言所寫成的範例,從動態語言、函數式程式設計等領域開展您的學習視野。 本書精華: ‧物件導向與抽象化 ‧......一起来看看 《松本行弘的程式世界》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具