c 语言的基本知识

  • 要学习写什么?

c 用指针管理内存,内存管理和数据解构算法都离不开指针

  1. 内存管理

  2. 算法/数据结构

语言发展

机器语言 -》 汇编语言 -》 FORTRAN 语言、Pascal语言(Delphi)、Basic语言(Visual Basic)、 C语言(C++ 、Java)

c 最早始是编写Unix内核的,现在主要用于做底层开发和嵌入式等

c 与 JavaScript 的异同

c | js --|--|--| c语言是编译型语言 | js 是解释性语言 c语言要借助编译器转换成可执行程序| js需要借助解释引擎运行

配置环境

Windows | Linux/unix | Mac --|--|--|--|--|-- visual C++/MingWin | gcc/g++ | Xcode/gcc

编译软件

nginx 为例,复制下载链接(pgp格式)

mkdir temp

cd temp  

wget -c https://nginx.org/download/nginx-1.16.1.tar.gz

tar -zvxf nginx-1.16.1.tar.gz

cd nginx-1.16.1   // 源代码都在这里的 src

// 当前目录下有configure

./configure    // 执行这个检查系统配置是否完善

// 如果执行之后当前目录出现 Makefile 这个文件,就可以执行编译了
make       // 开始编译

// 如果编译出错,需要重新编译 执行 ` make clean `, 删除之前的编译文件

// 编译后会在当前目录生成一个objs 文件夹,最终的可执行文件就在这个文件夹下的 nginx
make install    // 执行安装,之后文件就会被安装到指定位置了

敲一个hello world

// 安装 gcc, 如果没有  , centOs下

yum install gcc

// 编译c++ 需要
yum install gcc-c++

// ubuntu 下安装
apt-get install gcc

// 编译c++ 安装
apt-get install g++

写一个c文件 hello.c

/* 
    引入头文件,头文件里定义了一些必要的库  
    stdio   输入(标准输入--键盘)输出(标准输出--终端)库  
*/
#include <stdio.h> 

/**
    main 入口函数,程序在这开始执行,必须是main

    也可以写成 
    void main(){}

    int main(int argc, char const *argv[] ){
    int argc  // 参数的个数
    char const *argv[]  字符串数组

    \n  换行
    \r  回车

*/

int main(int argc, char const *argv[] ){
    printf("hello world! \n");
    return 0;
}

执行编译

gcc hello.c -o hello   // -o 后面接输出文件的名字

// 执行,即可打印
./hello

要学习的相关内容

  • cpu 与内存的工作原理
  • 机器语言与汇编语言
  • c 与 c++ 语言
  • 指针与内存的管理机制

学习 c 语言的什么

  • 编译开源软件的源代码 (gcc、make)
  • c 语言基本语法
  • 指针和 c 语言那些坑
  • 从内存层面理解数据结构
  • 搞懂 v8 的内存管理机制
  • 分析 webkit 引擎源代码
  • 用 c 编写 node 和 PHP 扩展模块

CPU 是怎样工作的

  • CPU 的指令集

    • x86、x64 指令集 (专利,指定商家才有)
    • ARM 指令集 (付费, 华为、树莓派)
    • mips 指令集 (接触不到)
    • risc 指令集 (接触不到)
  • CPU 的组成

    • 控制器(控制单元)

      发出控制指令

    • 运算器(运算单元)

      二进制加法(只做加法)

    • 存储器(存储单元)

      存放要计算的数据和计算结果

内存 CPU ------------ 进程 ---------- 指令地址 | |-------------(控制单元) 代| 指令1 <------------------|-----|- 指令计数器 (把要用的每条指令传给指令寄存器) 码| 指令2 指令 | | 段| 指令3 -------------------|-----|-->指令寄存器 (相当于计算公式) 指令4 | ------------------ 指令5 | ⇓ ⇓ 。。。 | ⇓ ⇓ 操作数据地址 | ⇓ 控制指令 ⇓ 数| 数据1 <--------------------------|---------| |---------| 据| 数据2 | | 存储 | 数据 | 运算 | 段| 数据3 数据 | | 单元 | <----> | 单元 | 数据4 <---------------------->| | | | | ---------- ---------- # 内存类似一个大的一维数组,其中包含若干个存储单元,一个存储单元 1 Byte(包含8个 bit,8个二进制位) # 内存理论上没有最大值,内存地址的开始为 0
  • 通用寄存器

    处理数据和指令

  • 内部通信寄存器

    控制数据总线(通过数据总线传送信息)

计算机语言的本源与发展

  • 本源: 二进制数据

  • 第一代语言:机器指令(痛苦指数 *****)

    纸带上打孔(光电设备读取)

  • 第二代语言:汇编指令(痛苦指数 ****)

    助记符 + 伪指令

  • 第三代语言:高级语言(主要特征:面向过程)

    更符合人类思维习惯,比较好用了

  • 第四代语言:面向对象语言(面向互联网、天然支持数据库)

汇编语言

  • 汇编语言是最贴近底层的计算机语言
  • 直接操作硬件的,没有任何抽象
  • 由指令与数据组成,没有任何语句
  • 受到硬件平台限制,可移植性很低
  • 了解一些底层语言知识,对理解计算机的运作机制和内存管理大有好处

体验汇编语言

  • linux 安装 nasm

  • nasm -f elf64 a.asm

  • ld a.o -o a

  • 执行 ./a 输出 Hello world

section .data hello: db 'hello world!/n',10; helloLen: equ $-hello ;'hello world' 字符串长度 section .text global _start _start: mov ax,4; 4 : sys_write 系统调用号 mov ebx,1; 1: 标准文件输出描述符 mov ecx,hello; 放hello 字符串的首地址 mov edx,helloLen; hello 字符串长度 int 80h; 软中断,陷入内核 mov ax,1; sys_exit 系统调用号 mov ebx,0; 返回值, 0 表示没有错误 exit(0) int 80h;

c 语言

  • 天生的系统级语言

  • 最早用来编写 unix 内核

  • 曾经最流行的语言

  • 至今仍在不断发展

    |---> fortran |---> pascal ---> delphi | 机器语言 --> 汇编语言 -->| |---> basic ---> visual basic |---> c --> | 低级语言。 |--> c++ |--> java # 从左到右运行速度逐渐降低。 高级语言。

c 和 js 的异同

  • c 是编译型语言(编译器 gcc msvc)
  • js 是解释型语言(jit 可以将 js转成机器语言代码)
  • c 要借助编译器转成可执行文件
  • js 要借助解释引擎运行

c 与 c++ 区别

  • c++ 是新的编程语言,并不是 c 的扩展,但是兼容c

  • c 是面向过程的, C++ 是面向对象的

  • c 和 C++ 都有标准库

  • 目前 c 大多用在网络相关和嵌入式等方面

  • 目前 c++ 大多用在复杂引擎和应用软件方面

c 内存处理手动

c++ 半手动

go Gc

rust 手动 + Gc

配置环境

  • windows 环境:Visual C++/MingWin
  • linux/unix: gcc/g++
  • Mac: Xcode/gcc
  • 推荐 linux、unix、mac

理解内存和指针

  • 内存与内存地址

    内存类似一个大的一维数组,包涵若干个存储单元(1Byte = 8bit)

    内存地址用16进制表示 如 0x20e958

  • 指针、地址与引用

    指针:一个变量,保存内存存储的地址。即保存地址的变量。 在32位系统中 占用 4个字节,在64 位系统中占 8个字节。这就是为啥 32位程序小于64位程序。

    地址:如 0x20e958

    引用:对指针的一个封装

  • 指向变量的指针

    指针表示方式

    // 类型确定了 数据占用内存的大小
    int a = 120
    
    // 指针 用 * 表示,在变量前加 &
    int * p_int = &a
    
    // 指向指针的指针 即二级指针
    int ** pp_int = &p_int
    // 三级指针
    int *** ppp_int = &pp_int
    
    // 指向函数的指针
    (返回值类型 (函数名)  (参数类型, 参数类型。。。)
    (int (*)(char *, int))
    
    // void 类型指针
    typedef struct {
        char a,
        int b,
        float c
    } ss
    ss d;
    // 生命一个void 指针
    void * P_void = &d
    // 使用指针的值 使用的时候要标注好类型
    (ss*) p_void
  • 指向指针的指针

  • 指向函数的指针

  • 动态内存分配

内存动态分配

指针可以进行加减运算,不能做乘除运算

  • 指针和内存

    每块内存都有2个部分,地址和值。 一级指针的值,对应着指针指向的值的地址 二级指针的值,对应着一级指针的地址

    内存 ----------------------------- | 地址:oxee45, 值:12 | ---> int i = 12 ----------------------------- | 地址:oxee46, 值:oxee45 | ---> int * p = &i ----------------------------- | 地址:oxee47, 值:oxee46 | ---> int ** p1 = &p -----------------------------
  • 动态申请内存

    int a = 10  // 内存申请在栈上
    
    // 动态申请内存, 存在堆上, 内存大小 4 字节
    int * pb = malloc(sizeof(int))  
    
    // 释放内存
    free(pb)
    
    // c++ 动态申请写法, 内存申请在堆上
    int * pcpp = new int()
    
    // c++ 释放内存
    delete pcpp;
  • 内存 4 区

    • 栈区(内存会自动获取自动释放)

      是一个确定的常数(1-2M)不同平台大小不同,超出会提示 stackoverflow

    • 堆区(内存需要手动申请、释放)

      用于动态内存分配

    • 全局区或静态区(只初始化一次)

      在程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)

    • 程序代码区(代码区的指令中包括操作码和要操作的对象(或对象引用地址))

      代码区指令根据程序设计流程依次执行,对于顺序指令,对于顺序指令,则只会执行 一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈 来实现

    我们在平时使用的时候,只能操作 栈区、堆去、全局静态区,如果操作程序代码区,就会 发生保护性错误。