• C复习-基础知识


    参考:

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

    从hello.c到可执行文件hello

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

    root> gcc -o hello hello.c
    
    • 1

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

    1)预处理阶段:cpp根据#开头的命令,修改原始程序。比如#include 就是读取系统头文件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:原本是为了减少字符集规模,不过其实不太常用。

    #include 
    #include 
    
    void main() {
        printf("??) ??( ??! ??< ??> ??' ??= ??/ ??-");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    gcc -o hello hello.c -trigraphs
    ./hello
    ] [ | { } ^ #  ~
    
    • 1
    • 2
    • 3

    注释

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

    #if 0
    statements //要注释掉的代码
    #endif
    
    • 1
    • 2
    • 3

    数据

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

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

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

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

    int ch;
    while( (ch = getchar()) != EOF && ch != '\n' );
    
    • 1
    • 2

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

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

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

    printf("\127\n"); // W
    printf("\x00f\n"); // 如果数值较大会报错
    
    • 1
    • 2

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

    #include 
    ...
    wchar_t c = L'Xs';
    MessageBoxW(0, L"你好世界", L"I am", 0); // MessageBoxW默认以宽字符处理
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    枚举enumerated类型

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

    enum Jar_Type {CUP, PINT, QUART, GALLON}; // 声明类型
    enum Jar_Type milk_jug; // 声明枚举类型的变量
    
    
    • 1
    • 2
    • 3

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

    enum Jar_Type {CUP = 8, PINT, QUART, GALLON}; // 声明类型
    enum Jar_Type milk_jug = PINT; // 声明枚举类型的变量
    printf("%d", milk_jug); // 9
    
    • 1
    • 2
    • 3

    浮点数

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

  • 相关阅读:
    docker安装部署skywalking
    基于.NET6、FreeSql、若依UI、LayUI、Bootstrap构建插件式的CMS
    智慧校园管理在疫情防控中的作用有哪些?
    Open Office XML 格式里如何描述多段具有不同字体设置的段落
    弄懂#if #ifdef #if defined
    JWT 使用入门(一)配置与示例
    页面关闭前,如何发送一个可靠请求
    编辑器插件
    配置Jedis连接池
    数据库并发问题及四种隔离级别
  • 原文地址:https://blog.csdn.net/pxy7896/article/details/133862404