Yes

Keep It Simple, Stupid.

LearnGameEngine-d0

Posted at — Dec 8, 2020

Hello,C

语言和编译器

语言是用沟通表达的,C语言是人与机器沟通的桥梁,C是用英文写,实际上也是可以用中文来实现的,不过没必要汉化。

机器如何读懂语言?这需要讲很多的东西,简单说就是:人->写C语言代码->把代码输入编译器->编译器转换C语言为机器语言->机器。

人->写C语言代码: 这个是平时程序员的工作

写C语言代码->把代码输入编译器: 前面教程里安装环境其实就是安装的编译器,名叫GCC,GCC编译器是1987年造出来的,34岁了, 编译器拿到代码会做一些预处理。

编译器转换C语言为机器语言->机器: GCC编译器内部非常复杂,毕竟能直接把人类输入的文字转化成机器命令。编译后的程序需要在操作系统上运行,有的程序会有漂亮画面。

程序编译,操作系统,图形学(绘制画面)并称为程序三大浪漫,是所有工程师的最终理想。

GCC可以用于制作火箭,飞船,武器,车载设备,人工智能,机器人。

*也许我们能用GCC创建一个世界

C代码编写

我们打开终端,输入

gcc -v

可以看到gcc当前的版本号。

如何使用gcc?之前的视频中有一段环境测试使用的gcc/g++编译了一次,这次我们重新来看看。
1.打开终端
2.创建并进入目录 mkdir -p /data/code/d0 && cd /data/code/d0
3.创建代码文件 touch 1.c
4.使用Code命令编辑 Code ./1.c
5.输入代码内容:(注意不能使用中文符号,最好切换为英文输入法)

int i1()
{
    return 1;
}

现在我们写好了一个简单的函数,int代表返回值是数字,i1为函数名字,()代表参数列表,{}包裹的是函数体,函数体内包含了句代码 return 1,return关键字的意思是函数执行后,把后面的数字1返回,函数名字i1前面的就是返回值类型,int代表数字。

这个函数的功能很简单就是返回一个数字1

C代码编译

机器是不认识我们人类的语言代码的,我们需要进行编译,把它进行转换为机器可读的形态,我们使用gcc对它进行编译:
1.保存1.c
2.切换到终端执行命令:gcc -c 1.c
3.使用ls命令查看下文件:ls
我们可以看到输出:

$ ls
1.c  1.o

目录下多了一个1.o,(注意后缀名是字母o)1.o就是强大的gcc帮我们转换出来的机器专用的文件。
我们可以用查看文件内容的命令:cat命令查看下1.c的内容,cat ./1.c,我们看到输出结果是:

$ cat 1.c
int i1()
{
    return 1;
}

cat命令把文本内容输出出来了,再使用cat命令查看下1.o看看机器读文字到底是什么样的:

$ cat 1.o
d▒v.text P`.data@P▒.bss▒P▒.xdat@0@.pdata
                                        X@0@/40(@P@UH▒▒]Ð▒▒▒▒▒▒▒▒PGCC: (Rev5, Built by MSYS2 project) 10.2.0
.file▒▒g1.cadd .text.data.bss.xdat.pdata
                                        +.rdata$zzz.rdata$zzz

意外吗?机器读的文字虽然很乱,但是我们居然可以看懂一部分,以前我们听说机器都是只认识二进制010101的,里面为什么不是01?因为cat指令把二进制文件用转换成了乱码文本,实际需要二进制查看器来查看1.o文件,才会显示它的二进制形态,程序员习惯使用16进制来阅读二进制。
你可以用终端执行hexdump ./1.o来查看它的16进制形态:

$ hexdump 1.o
0000000 8664 0006 0000 0000 0176 0000 0010 0000
0000010 0000 0004 742e 7865 0074 0000 0000 0000
0000020 0000 0000 0010 0000 0104 0000 0000 0000
0000030 0000 0000 0000 0000 0020 6050 642e 7461
...
...

好了,现在我们成功编译了函数int i1()。

程序增加函数

这个程序就只包含了一个函数int i1(),我们再加点函数。
1.使用Code命令编辑 Code ./1.c
2.更改代码内容:(注意不能使用中文符号,最好切换为英文输入法)

int i1()
{
    return 1;
}

int getint2()
{
    return 2;
}

重新编译:
1.保存1.c
2.切换到终端执行命令:gcc -c 1.c
我们的1.o被更新了,现在它是一个包含了两个函数的文件了。
1.o和电影.mp4这些都不能用cat命令或者文本编辑器查看正确内容,会乱码,mp4需要播放程序打开,这样的不可阅读的文件我们称呼它为:bin(二进制)文件。

标准程序

上面的1.o二进制文件为什么不是1.exe,不是exe程序才能执行吗像QQ.exe,微信.exe?我好像从来没见过.o后缀名的程序。

是的o文件不能执行,因为它差了入口函数。为什么要设计入口函数?你有两个函数int i1()和int getint2(),当我们打开1.o的时候该执行哪个呢?操作系统并不知道,所以系统提供了一个入口标志函数:int main(),你想让程序能运行必须要有main函数,系统会从main开始执行你的程序

让我们写一个新的代码文件,原来的1.c留着后面用。

1.创建代码文件 touch 2.c
2.使用Code命令编辑 Code ./2.c
3.输入代码内容:(注意不能使用中文符号,最好切换为英文输入法)
int main()
{
    return 0;
}
1.保存2.c  
2.切换到终端执行命令:gcc -c 2.c

编译完成后,使用ls命令查看文件发现依然是2.o?我要的exe程序呢?
不用着急,编译器需要两个步骤来生成exe。

链接

编译器需要先编译生成.o,再把.o链接为.exe
进入终端执行:

gcc 2.o

ls查看目录发现exe出来了,它叫a.exe!

$ ls
1.c  1.o  2.c  2.o  a.exe

终端输入./a.exe执行它,发现什么都没有,因为我们没写什么代码。

库的概念就是中间件。我要做饭,但我不会,就要求库帮我做好,有的库要钱,有的库不免费,有的库甚至会往里面放毒。

认识第一个库stdio.h
stdio.h里面包含了很多输入输出相关功能,这些功能都是人们写的,至于写的人是谁,也许是你祖父级的那么大岁数的前辈们写的,在他们年轻的时候写的。stdio.h库是一个标准库,当你写代码能力很强,写的代码很好用,全世界都要用,那么你可以把代码发给标准委员会,通过会议层层投票,你的库会变成标准库!

使用stdio.h库,使用库的步骤第一件事:在你的代码文件中添加一行这样的代码#include<库名字>,现在添加stdio.h库,修改我们代码2.c为:

#include <stdio.h>

int main()
{
    printf("%d", 666);
    return 0;
}

我们添加了两行代码,一行include 和一行printf,include就是添加库的意思,printf是stdio.h提供的一个函数,printf可以显示一段话,例子中把数字形666转换成了文本形,并显示:

    printf("%d", 666);

进入终端编译运行下试试:

1. 保存2.c
2. gcc -c 2.c
3. gcc 2.o
4. ./a.exe

得到了输出:

$ ./a.exe
666

注释

如果我们的代码需要给别人阅读,别人读不懂你写的中式英语怎么办?
C语言提供了这种语法来添加备注/注释,注释的代码不会被GCC编译:

#include <stdio.h>

int main()
{    // 左{括号是函数体的开始
    printf("%d" , 666); // 这里是向终端输出文本的意思,输出整数666
    printf("%f" /*%f是小数*/ , 666.1); // 这里是向终端输出文本的意思,输出整数666
    


    //printf(a, b) a是格式,b是代人a的值

    printf("%f"      // %f是小数
        , 666.1      /*小数值*/
    ); 

    return 0; // main函数返回
    printf("%d" , 666);  //这行代码在返回值后面,所以执行不到!

} // }括号是函数体的结束

我们修改b.c为上面的代码,编译运行后就会得到结果:

$ ./a.exe
666666.100000666.100000

看起来很糟糕,他们几次的printf都连在一起了。我们在前面两的%d和%f后面加个 | 竖线:

#include <stdio.h>

int main()
{
    printf("%d | " , 666);   //这里是向终端输出文本的意思,把%d换为整数666并输出
    printf("%f | " , 666.1); //输出把%f换为666.1并输出小数
    
    printf("%f"
        , 666.1
    ); 
    return 0; // main函数返回
    printf("%d" , 666);  //这行代码在返回语句后面,所以执行不到!
}

编译运行后:

$ ./a.exe
666 | 666.100000 | 666.100000

更多用法:

#include <stdio.h>

int main()
{
    printf("%d | " , 666);
    printf("%f | " , 666.1); 
    
    printf("%f"
        , 666.1
    ); 
    printf("%s",  "\n"); //换行符号
    printf("混合输出 小数%f 整数%d 小数%f",  1.0, 2, 3.0);
    printf("%s",  "再换行\n");
    printf("%s",  "换行后"); 
    printf("%s",  "\n"); 
    printf("%s",  "换\n行"); 
    return 0; // main函数返回

    printf("%d" , 666);  //这行代码在返回语句后面,所以执行不到!
}

参照运行结果对照代码看下printf的用法。

日常Tips

编译占用CPU,我们项目中百万行代码一个CPU编译大概1小时,使用8个CPU可以大幅度降低时间。
链接占用磁盘,换上固态硬盘可以降低时间。

还记得我们的硬盘教程中破解软件的演示吗?
我们只要用16进制编辑器修改程序里面的数字,就可以完成破解,但需要较高的技术功底。
另外FBI Warning