itpestudy c c++

C 概述

计算机组成

计算机基本结构为 5 个部分,分别是运算器、控制器、存储器、输入设备、输出设备,这 5 个部分也被称为冯诺依曼模型。

Alt text

accumulator
(computer science) a register that has a built-in adder that adds an input number to the contents of the register

arithmetic logic unit
/əˈrɪθmətɪk/
the type of mathematics that deals with the adding, multiplying, etc. of numbers

Alt text

控制单元负责控制 CPU 工作,逻辑运算单元负责计算,而寄存器可以分为多种类

通用寄存器,用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。
程序计数器,用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令「的地址」。
指令寄存器,用来存放当前正在执行的指令,也就是指令本身,指令被执行完成之前,指令都存储在这里。

一个程序执行的时候,CPU 会根据程序计数器里的内存地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。

总线是用于 CPU 和内存以及其他设备之间的通信,总线可分为 3 种:

地址总线,用于指定 CPU 将要操作的内存地址;
数据总线,用于读写内存的数据;
控制总线,用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线;

当 CPU 要读写内存数据的时候,一般需要通过下面这三个总线:

首先要通过「地址总线」来指定内存的地址;
然后通过「控制总线」控制是读或写命令;
最后通过「数据总线」来传输数据;

指令

Alt text

MIPS 的指令是一个 32 位的整数,高 6 位代表着操作码,表示这条指令是一条什么样的指令,剩下的 26 位不同指令类型所表示的内容也就不相同,主要有三种类型R、I 和 J。

R 指令,用在算术和逻辑操作,里面有读取和写入数据的寄存器地址。如果是逻辑位移操作,后面还有位移操作的「位移量」,而最后的「功能码」则是再前面的操作码不够的时候,扩展操作码来表示对应的具体指令的;
I 指令,用在数据传输、条件分支等。这个类型的指令,就没有了位移量和功能码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或一个常数;
J 指令,用在跳转,高 6 位之外的 26 位都是一个跳转后的地址;

指令从功能角度划分

数据传输类型的指令,比如 store/load 是寄存器与内存间数据传输的指令,mov 是将一个内存地址的数据移动到另一个内存地址的指令;
运算类型的指令,比如加减乘除、位运算、比较大小等等,它们最多只能处理两个寄存器中的数据;
跳转类型的指令,通过修改程序计数器的值来达到跳转执行指令的过程,比如编程中常见的 if-else、switch-case、函数调用等。
信号类型的指令,比如发生中断的指令 trap;
闲置类型的指令,比如指令 nop,执行后 CPU 会空转一个周期;

Alt text

加和运算 add 指令是属于 R 指令类型:

add 对应的 MIPS 指令里操作码是 000000,以及最末尾的功能码是 100000,这些数值都是固定的,查一下 MIPS 指令集的手册就能知道的;
rs 代表第一个寄存器 R0 的编号,即 00000;
rt 代表第二个寄存器 R1 的编号,即 00001;
rd 代表目标的临时寄存器 R2 的编号,即 00010;
因为不是位移操作,所以位移量是 00000

key words

enum
enumerate
枚举
to name things on a list one by one

extern
external

volatile
/ˈvɑːlətl/
易变的
change suddenly and unexpectedly.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>  // 引入头文件
// stdio.h系统标准输入、输出头文件。即引入printf函数
// <> 系统同文件 "" 用户自定义头文件

int main(void) // main函数,程序的唯一入口
{
printf("hello world!\n");
system("pause"); // 执行系统命令,其他如清空屏幕system("cls");

return 0; // 0 表示程序正常结束
}

system("pause");

Alt text

gcc

windows explore
地址栏选择路径 —》 直接输入CMD
以当前文件夹地址打开CMD

gcc helloworld.c -o helloworld.exe
helloworld.exe

linux gcc

vi hello.c
insert
esc -> :wq

apt install gcc
gcc hello.c -o hello
./hello

Alt text

预处理

gcc -E hello.c -o hello.i

头文件展开

宏定义替换:将宏名替换为宏值

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>  // 引入头文件
#define PI 3.14 //定义常量,宏定义
// stdio.h系统标准输入、输出头文件。即引入printf函数
// <> 系统同文件 "" 用户自定义头文件

int main(void) // main函数,程序的唯一入口
{
printf("%d\n", PI);
system("pause");

return 0; // 0 表示程序正常结束
}

替换注释为空行

展开 条件编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>  // 引入头文件
#define PI 3.14 //定义常量,宏定义
// stdio.h系统标准输入、输出头文件。即引入printf函数
// <> 系统同文件 "" 用户自定义头文件

int main(void) // main函数,程序的唯一入口
{
#ifdef PI
printf("PI exits");
#endif // PI

printf("%d\n", PI);
system("pause");

return 0; // 0 表示程序正常结束
}

编译

检查语法错误
生成汇编语言的汇编文件

gcc -S hello.i -o hello.s

汇编

将汇编指令翻译成二进制机器编码

gcc -c hello.s -o hello.o

链接

gcc hello.o -o hello

数据段合并
数据地址回填
库引入

printf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int main(void)
{
int a = 3;
int b = 5;
int c;
c = a + b;

printf("%d\n", c);

printf("c = %d\n", c);

printf("%d + %d = %d\n", a, b, c);

printf("%d + %d = %d\n", a, b, a + b);

return 0;
}

8
c = 8
3 + 5 = 8
3 + 5 = 8

debug

Alt text

f11 逐语句,会进入函数内部
f10 逐过程,不会进入子函数

调试 —》 窗口 —》 反汇编

Alt text

visual studio

ctrl + k, ctrl + f 格式美化代码
ctrl + k, ctrl + c 注释
ctrl + k, ctrl + u 取消注释

常量

“hello”, ‘A’, -10, 3.1415
#define PI 3.1415 //推荐
const int a = 10; //只读变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#define PI 3.1415

int main(void) {
const int r = 3;
const int a = 10;
float s;
// a = 50;
//PI = 3.15;
s = PI * r * r;
float l = 2 * PI * r;
printf("s = %.2f, l = %f\n", s, l);
return 0;
}

printf(“s = %.2f, l = %f\n”, s, l);
%.2f 保留2位小数,会进行四舍五入

extern 关键字, 只是声明外部符号(标识符)
extern int a;

sizeof关键字

返回unsigned int类型
printf("size of int = %u\n", sizeof(int));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

int main(void) {
printf("size of int = %d\n", sizeof(int));
printf("size of short = %d\n", sizeof(short));
printf("size of long = %d\n", sizeof(long));
printf("size of long long = %d\n", sizeof(long long));
int a = 10;
short b = 20;
long c = 30;
long long d = 40;
printf("size of a = %d\n", sizeof(a));
printf("size of b = %d\n", sizeof(b));
printf("size of c = %d\n", sizeof(c));
printf("size of d = %d\n", sizeof(d));
unsigned int e = 10;
unsigned short f = 20;
unsigned long g = 30;
unsigned long long h = 40;
printf("size of e = %d\n", sizeof(e));
printf("size of f = %d\n", sizeof(f));
printf("size of g = %d\n", sizeof(g));
printf("size of h = %d\n", sizeof(h));
}

size of int = 4
size of short = 2
size of long = 4
size of long long = 8
size of a = 4
size of b = 2
size of c = 4
size of d = 8
size of e = 4
size of f = 2
size of g = 4
size of h = 8

int a = 10;
sizeof int
sizeof a

Alt text

char

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void) {
char ch = 'A';
printf("ch = %c\n", ch);
printf("ch = %d\n", ch);

ch = 97;
printf("ch = %c\n", ch);
system("pause");
return 0;
}

ch = A
ch = 65
ch = a

\0

char a = ‘\0’;
printf(“a = %d\n”, a); // 0

Alt text

浮点数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main(void) {
float a = 3.1415f;
double b = 13.45687;
float c = 3.2e3f;
float d = 1.6e-3f;

printf("a = %.3f\n", a);
printf("b = %08.3lf\n", b); //总字符数为8(包含小数点),不足的0填充,保留3位小数,最后一位为四舍五入的。
printf("c = %.3f\n", c);
printf("d = %.3f\n", d);
return 0;
}

a = 3.141
b = 0013.457
c = 3200.000
d = 0.002

除2方向取余法 10 –> 2

原码,反码,补码

原码:最高位为符号位

反码:
正数与原码相同
负数,最高位为1,其他位取反

补码:为计算机中存储负数的形式
正数与原码相同
负数,最高位为1,其他位取反 + 1
00000000 为 0
11111111 为 -128