关于内存布局的认知

在没有运行程序前,或者说程序未加载到内存前,可执行程序内部已经分好3段信息,分别为代码段text/code,数据段data,未初始化数据段bss3 个部分. 程序在加载到内存前, code segment,data,bss的大小就是固定的.当运行可执行程序,系统把程序加载到内存后,除了根据可执行程序的信息分出代码段,数据段和未初始化数据段之外,还额外增加了栈区与堆区. 代码段(code segment) 代码段存放 CPU 执行的机器指令.通常代码段是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可. 代码段通常是只读的(如x86/Arm体系),使其只读的原因是防止程序意外地修改了它的指令.但特定的体系结构允许自修改代码段(如IBM/360型号可以实现Self-modifying code). 代码段一般放在堆或堆栈的下面,以防止堆和栈溢出覆盖到它. 数据段(data segment) 此段含了程序中已初始化的全局变量,静态变量(包括全局静态变量和局部静态变量)和常量数据,它有的部分是只读,有的部分是可读可写. 所有已初始化且含有const修饰的数据通常是在.rodata段(也可能是code segment). 在程序运行后,其生存周期为整个程序运行过程. 未初始化数据段(bss segment) bss存入的是全局未初始化变量和未初始化静态变量,未初始化数据区的数据在程序开始执行之前被内核初始化为0或者空NULL,其生存周期为整个程序运行过程. 栈区(stack segment) 栈是一种先进后出的内存结构.由编译器自动分配释放,存放函数的参数值/返回值/局部变量等.在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间. 堆区(heap segment) 堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序. 堆用于动态内存分配.堆在内存中介于BSS区和栈区之间.一般由程序员分配和释放.若程序员不释放,程序结束时由操作系统回收.

Read More

关于结构体和联合体的认知

结构体的定义和初始化 声明结构体类型成员再定义此结构体的名称. 可以在声明类型的同时定义变量并直接初始化(也可以不初始化只定义变量). 可以直接定义结构体类型变量(无类型名,即匿名结构体声明). 只有在定义结构体变量的时候才可以初始化. 结构体类型和结构体变量关系: – 结构体类型: 指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元.可以看作类似变量的声明,但实际是定义. – 结构体变量: 根据定义的结构体类型(内部成员的类型,数量和大小)为之分配空间. 结构体的赋值 普通结构体在使用时候,通过.运算符操作成员. 如果是指针结构体变量,则通过->运算符操作成员. 位域 位域(位段)的特点: 1. 位域声明和结构体类似. 2. 位域的成员必须是整型类型(包括枚举类型),如:int,unsigned int,short,unsigned short,long等; 3. 位域的成员名后边有一个冒号和一个数字: type [var]: digits,代表所占大小. 位域的优点: – 能够把长度为奇数的数据包装在一起,节省存储空间. – 方便访问整形值的部分内容. 位域的缺点: – … “关于结构体和联合体的认知”

Read More

关于作用域的认知

所谓作用域,其实就是指变量作用的范围. C语言变量的作用域按照类型可分为: 1. 代码块作用域(代码块是{}之间的一段代码) 2. 函数作用域 3. 文件作用域 普通局部变量 局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点: – 在一个函数的形参或在其内部定义(即{}内),则只在本函数范围内有效. – 在复合语句中定义,只在复合语句中有效. – 只有在运行到定义变量的位置,才会开始分配空间. – 在函数调用结束或复合语句结束后局部变量的生命周期也结束. – 变量如果没有赋初值,则其内容为随机值. int i; // 等同于 auto int i; static修饰的局部变量 static局部变量生命周期和普通局部变量不一样,其它用法和普通局部变量一样 程序运行到static局部变量初始化的语句时,才进行初始化;下次执行到,则不再初始化.因此static局部变量只初始化一次,可以赋值多次. static局部变量的作用域也是在定义的函数内有效 static局部变量的生命周期和程序运行周期一样.在函数没有调用前,static局部变量就已经存在;函数调用完毕,static局部变量还存在.不释放,只有在整个程序结束才释放. static局部变量只能用常量初始化,不能用变量初始化.若未赋以初值,则由系统自动赋值: 数值型变量自动赋初值0. 字符型变量赋空字符’\0′. … “关于作用域的认知”

Read More

关于指针的认知

指针 指针的概念 内存区的每一个字节都有一个编号,这就是“地址”.如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号). – 指针的实质就是内存“地址”.指针就是地址,地址就是指针. – 指针是内存单元的编号,指针变量是存放地址的变量. – 指针也是一种数据类型,指针变量也是一种变量. – 指针变量指向谁,就把谁的地址赋值给指针变量. – “*”操作符操作的是指针变量指向的内存空间. 需要注意的是&符号可以取得一个变量在内存中的地址,但是不能取寄存器变量.因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的. 指针的大小 指针的大小可以使用sizeof()测得,而sizeof()测得的是指针变量指向存储地址的大小. 不同的平台下的指针,其大小是不同的: – 在32位平台,所有的指针地址都是32位(4字节). – 在64位平台,所有的指针地址都是64位(8字节). 空指针和野指针 指针变量也是变量,是变量就可以任意赋值,只要其存储的值不要越界即可(32位为4字节,64位为8字节).但是,当将任意数值赋值给指针变量时,这时的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域).所以,野指针并不会直接引发错误.但当操作野指针指向的内存区域才会出问题. 但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中可以把NULL赋值给此指针,这样就标志此指针为空指针.没有任何指向.而NULL是一个值为0的宏常量. 万能指针 void* void *指针可以指向任意变量的内存空间. 关于const修饰指针的不同情况 当const修饰指针*时,指针指向内存区域不能修改,指针指向可以修改. 当const修饰变量名时,指针指向不能修改,指针指向的内存可以修改. 数组和指针的关系 数组名字是数组的首元素地址,但它是一个常量.可以用指针间接操作数组.即定义一个变量指向数组的地址. 指针数组,即指针的数组.它是数组,数组中的每个元素都是指针类型. 数组指针,即数组的指针.它是指针,它代表指向一个数组的指针. … “关于指针的认知”

Read More

关于数组的认知

数组 数组的概念: 数组就是在内存中连续的相同类型的变量空间.同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的. 数组属于构造数据类型,一个数组可以分解为多个数组元素,按数组元素类型的不同,数组可分为:数值数组/字符数组/指针数组/结构数组等类别. 数组的初始化 需要注意的是,在定义数组时,下标中的数只能是常量,而在使用数组时,下标中的数可以是常量也可以是变量.而定义数组的同时进行赋值时,称之为初始化.若全局数组仅声明而不初始化,则编译器默认将数组中的值初始化为0.若仅局部数组未初始化,则其值为随机. 数组名 数组名既是一个地址的常量,同时也指向数组中首元素的地址,它还包含了数组的大小. 多维数组 根据数组的下标数,可分为一维数组或多维数组,而下标中的数则代表数组中的第某个元素. 数组类型 数组名 [n1][n2]…[nn]; 字符数组与字符串数组 By the way,在C语言中没有字符串这种数据类型,可以通过char的数组来替代.并且字符串一定是一个char的数组,但char的数组未必是字符串.以0结尾的char数组就是一个字符串,但如果char数组没有以0结尾,那么就不是一个字符串.所以字符串是一种特殊的char的数组.

Read More

关于类型转换的认知

数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题. 类型转换存在两种方法: – 隐式类型转换(自动转换): 遵循一定的规则,由编译系统自动完成. – 强制类型转换: 把表达式的运算结果强制转换成所需的数据类型. 隐式类型转换 隐式类型转换(自动转换)的原则: 占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低. 强制类型转换 强制类型转换指的是使用强制类型转换运算符,将一个变量或表达式转化成所需的类型,基本语法格式如下所示: (类型) (表达式) 示例: int i = 4; short s; s = short (i); 强制类型转换需要注意值域大向值域小的转换时,数据丢失的问题.

Read More

关于原码反码补码的认知

所有的原码反码补码都是在二进制的角度看. 原码 所谓原码,即一个数的原始二进制,其有以下特性: – 最高位为符号位,0表示正,为1表示负. – 其它数值部分就是数值本身绝对值的二进制数. – 负数的原码是在其绝对值的基础上,最高位变为1. 使用原码存储带来2个问题: – 0存在正负两种状态. – 两个数相减,结果错误. 1-1 = 1 + (-1) +1: 0000 0001 -1: 1000 0001 结果: 1000 0010 = -2(Dec) 反码 反码有以下特性: – 对于正数,反码与原码相同. – … “关于原码反码补码的认知”

Read More

C语言嵌套汇编代码混编

#include <stdio.h> int main() { int a; int b; int c; __asm { mov a, 12h //3的值放在a对应内存的位置 mov b, 12 //4的值放在a对应内存的位置 mov eax, a //把a内存的值放在eax寄存器 add eax, b //eax和b相加,结果放在eax mov c, eax //eax的值放在c中 } printf(“%d\n”, … “C语言嵌套汇编代码混编”

Read More

gcc编译器认知

gcc(GNU Compiler Collection,GNU 编译器套件),是由 GNU 开发的编程语言编译器, gcc原本作为GNU操作系统的官方编译器,现已被大多数类Unix操作系统(如Linux/BSD/MacOS等)采纳为标准的编译器,gcc同样适用于微软的Windows. gcc最初用于编译C语言,随着项目的发展gcc已经成为了能够编译C/C++/Java/Ada/fortran/Object C/Object C++/Go语言的编译器大家族. gcc的安装极为方便,如果是Ubuntu平台则直接apt install -y gcc,如果是CentOS/Fedora,则直接yum install gcc即可.当然也可以去官网下载二进制的安装包放到系统路径下即可. gcc命令的使用格式如下: gcc [options] file… 命令,选项和源文件之间使用空格分隔. 一行命令中可以有零个,一个或多个选项. 文件名可以包含文件的绝对路径,也可以使用相对路径. 如果命令中不包含输出可执行文件的文件名,可执行文件的文件名会自动生成一个默认名,Linux平台为a.out,Windows平台为a.exe. gcc编译的四个阶段: option Meaning -E 头文件展开,去注释 -S 编译 -c 汇编 -o 链接

Read More

关于C语言基础认知

为什么会出现语言? 因为需要交流.而在哥看来,交流的根本就是为了解决问题. 无论是汇编语言,C语言或是其他的程序语言,它们也是如此.为了解决人类与计算机交互产生的一种语言.如今的计算机遍布全球,除了人和人的相互交流之外,我们必须和计算机交流,让其识别我们的指令,满足所要的需求. 每种语言都有独特的语法和规则,而交流的双方则必须遵循此语法与规则才可以相互交流. 程序和指令 程序语言在经过编译器翻译后都会生产对应的一条或多头指令,每条指令则对应CPU特定的动作. 指令是对计算机进行程序控制的最小单位. 所有的指令的集合称为计算机的指令系统. 程序是为完成一项特定任务而用某种语言编写的一组指令序列. C语言的简洁 C语言仅有32个关键字,9种控制语句,34种运算符,却能实现无数的功能. 32个关键字: 数据类型关键字(12个): char, double, enum, float, int, long, short, signed, union, unsigned, void 控制语句关键词(12个): if, else, switch, case, default, for, do, while, break, continue, … “关于C语言基础认知”

Read More