C复习-基础知识

参考:

  1. 里科《C和指针》
  2. Bryant, Hallaron 《深入理解计算机系统》
  3. 何昊,叶向阳《程序员面试笔试宝典》

从hello.c到可执行文件hello

在Unix系统中,从源文件到目标文件的转化是由编译器驱动程序完成的:

shell 复制代码
root> gcc -o hello hello.c

这个转化可以分为4个阶段,执行这4个阶段的预处理器、编译器、汇编器和链接器一起构成了编译系统compilation system。

1)预处理阶段:cpp根据#开头的命令,修改原始程序。比如#include <stdio.h>就是读取系统头文件stdio.h的内容,直接插入到程序中。输出结果以 .i 后缀

2)编译阶段:编译器ccl将文本翻译成汇编语言。汇编语言是一个通用的低级机器语言指令。输出结果以.s后缀

3)汇编阶段:汇编器as将汇编语言翻译成机器语言,并且打包成可重定位目标程序relocatable object program的格式,输出结果以.o后缀

4)链接阶段:因为hello调用了printf函数,而它是标准C库的一个函数,因而有一个名为printf.o的目标程序,因此需要使用链接器合并,最终变成可执行文件。

注意:

1)一般UNIX系统中目标文件名后缀是.o,但MS-DOS中则是.obj

2)UNIX系统中,如果编译的源文件只有一个,中间产生的.o文件会在产生完可执行文件.out后被自动删除;但如果源文件有多个,不会删除,这样的话如果改动某个源程序,编译器会只重新编译改动过的,后面再一起链接。MS-DOS在单个源文件时也不会删除目标文件

linux键入./hello

1)键盘键入后,USB控制器将输入通过I/O总线经过I/O桥读入寄存器,然后再经过I/O桥、内存总线存入内存。

2)当键入回车时,shell知道输入结束了,随后加载可执行的hello文件,这些指令将代码和数据从磁盘复制到主存(使用直接存储器存取DMA技术能将数据直接从磁盘复制到主存)

3)代码和数据加载完成后,CPU开始执行指令,最后将输出"hello, world\n"从主存复制到寄存器文件,再经过总线接口、I/O桥到达显示器

GNU项目

GNU项目(GNU's Not Unix,1984年提出)已经开发出一个包含Unix操作系统的所有主要部件的环境,但内核除外(由Linux项目独立发展)。GNU环境包括EMACS编辑器、GCC编译器(GNU Compiler Collection)、GDB调试器、汇编器、链接器、处理二进制文件的工具喝其他一些不见。

GCC可以使用不同版本的C语言编译程序,参数是-std=xx

C版本 GCC命令行值
GNU89 无,-std=gnu89
ANSI, ISO C90 -ansi, -std=c89
ISO C99 -std=c99
ISO C11 -std=c11

C90被称为C89是因为它的标准化工作是从1989年开始的。

ANSI C的任何一种实现中,都有两种环境。一是翻译环境 translation environment,即将源代码转换为可执行的机器指令;二是执行环境 execution environment,用于实际执行代码。这两种环境不必在同一台机器上。交叉编译器 cross compiler就是在一台机器上编译,但是产生的可执行代码可以在不同机器使用。独立环境freestanding environment是指不存在操作系统的环境,比如嵌入式系统(微波炉控制器)


编码规范

字符

三字母词trigraph:原本是为了减少字符集规模,不过其实不太常用。

c 复制代码
#include <stdio.h>
#include <stdlib.h>

void main() {
    printf("??) ??( ??! ??< ??> ??' ??= ??/ ??-");
}

编译+执行。默认是不开启的,gcc要使用-trigraphs开启

powershell 复制代码
gcc -o hello hello.c -trigraphs
./hello
] [ | { } ^ #  ~

注释

如果需要注释掉一段代码,如果代码里有长段注释,可能/**/的效果不好,此时可以考虑#if和#endif,这样更安全

c 复制代码
#if 0
statements //要注释掉的代码
#endif

数据

C中只有4种基本数据类型:整型、浮点、指针和聚合类型(如array和struct)。

整型包括:字符、短整型、整型和长整型。都有signed有符号和unsigned无符号两种版本。

标准只规定了长整型至少应该跟整型一样长,整型至少应该跟短整型一样长。因此,缺省的int是16位还是32位,通常是由编译器决定的。一般来说是字长。limits.h定义了不同的整数类型的最大最小值。

char本质是小整型值,缺省的char可能是signed char或者unsigned char,所以为了可移植起见,char变量的值应该在两者的交集中(比如ASCII),也可以显式声明(某些机器处理signed char更快,但是一些库函数的参数声明是char,显式声明可能有兼容性问题)。

c 复制代码
int ch;
while( (ch = getchar()) != EOF && ch != '\n' );

为什么要把ch声明为int?EOF是一个整型值,如果使用char可能导致EOF解释错误

在整型字面值后添加L或l可以让整数被解释long,U或u则可以解释为unsigned,UL也可以组合

十进制整型字面值在缺省情况下,它的类型是能容纳这个值的最短类型。八进制需要以0开头,十六进制需要以0x开头。当然,使用\转义的时候,八进制格式为\ddd,十六进制格式为\xddd(此时算字符常量,所以输出是字符)

c 复制代码
printf("\127\n"); // W
printf("\x00f\n"); // 如果数值较大会报错

宽字符常量wide character literal:当运行时环境支持一种宽字符集时,可以使用。比如使用Unicode字符集

c 复制代码
#include <windows.h>
...
wchar_t c = L'Xs';
MessageBoxW(0, L"你好世界", L"I am", 0); // MessageBoxW默认以宽字符处理

枚举enumerated类型

枚举类型的值是符号常量,不是字符串。声明枚举类型的时候,实际就给符号名赋予了整数值,如下面的CUP实际就是0,PINT是1,以此类推。

c 复制代码
enum Jar_Type {CUP, PINT, QUART, GALLON}; // 声明类型
enum Jar_Type milk_jug; // 声明枚举类型的变量

当然也可以给部分符号赋值,对于没被显式赋值的符号,就是前一个符号名的值+1

c 复制代码
enum Jar_Type {CUP = 8, PINT, QUART, GALLON}; // 声明类型
enum Jar_Type milk_jug = PINT; // 声明枚举类型的变量
printf("%d", milk_jug); // 9

浮点数

包括float、double和long double,它们的范围存储在float.h中。浮点数必须至少有一个小数点或者指数(E/e),浮点数字面值缺省情况下是double,除非后跟L/l表示是long double,或者后跟F/f表示是float。

相关推荐
湫ccc35 分钟前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust
湫ccc2 小时前
《Python基础》之基本数据类型
开发语言·python