一、计算机组成原理(内存)
1.内存编址
(1)内存在逻辑上就是一个一个的格子,这些格子可以用来装东西,也就是内存中的数据,每个格子都有一个固定的编号,这个编号0、1、2、3就是内存地址,这个内存地址(一个数字)和这个格子的空间是一一对应的并且是永久绑定的。
(2)cpu在内存或硬盘里面寻找一个数据时,先通过地址线找到地址,然后再通过数据线将数据取出来。
(3)在程序运行时,CPU只认识内存地址,而不关心这个地址所代表的空间在哪里以及分布,因为硬件的设计保证了只要有地址,就一定能找到这个盒子在哪里,所以内存单元有两个概念:地址和空间。
(4)内存编址是以字节为单位,每一个内存地址对应的内存大小的空间是固定的,就是一个字节。
2.内存寻址
- 逻辑地址:包含在机器语言指令中用来指定一个操作数或者一条指令的地址
- 线性地址(也称虚拟地址):是一个32位无符号整数,可以用来表示高达4G的地址,通常使用16进制数来表示,范围从0x00000000到0xffffffff
- 物理地址:用于内存芯片级内存单元寻址,他们从微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址由32位或36位无符号整数表示
3.不同位数的系统的寻址空间
32位/64位系统----32根/64根地址线----物理线用通电来记录----用1/0来编写地址线
- 首先从二进制说起,每一bit都有2种取值的情况,也就是说可以表示2个存储区间
- 那么n位2进制数可以表示的存储区间数位2^n种
- 每个存储单元是8bit = 1B
- 所以n位地址总线可以表示的存储区间位:2^n Byte
32位系统支持ram最大为2^32Byte即4GB
64位系统支持ram最大为2^64Byte即128GB
4.栈区内存的使用
先用高地址空间,再用低地址空间。
数组与变量之间的内存占据情况:
vs 6.0h环境下——空0个整型
gcc环境下——空一个整型
VS2013——VS2019——空1或2个整型
二、指针
注:相较于函数中的传值调用,传址调用更为优势,可以减少创建局部变量所开辟内存的使用,从而减少内存的占用。
数据在内存中的情况
- 采用十六进制来表示地址和其数值
- 一位表示一字节
- 使用第一个字节的地址表示变量的地址
- 数值表示从首位往后排列
-
执行同一个程序每次输出的变量地址不一样(地址空间随机化分布,相当于重新开辟一个空间)
#include<stdio.h>
void main()
{
int a = 13;
printf("%p\n", &a);
}
“&p”,&变量专门用来打印地址
指针变量创建
创建并格式化指针变量:数据类型* +名称 = &+对应变量的名称
例如:
int a = 10;
char ch = 'h';
int* p1 = &a;
//创建指针变量p1来储存a的地址
char* p2 = &ch;
//创建指针变量p2来储存ch的地址
指针的使用
#include<stdio.h>
void main()
{
int a = 13;
int* pa = &a;
*pa = 20;
//* 解引用操作
//*pa通过pa里面的地址来找a
printf("%d %d", a,*pa);
}
测量指针变量的大小
32位下指针变量为4字节
64位下指针变量为8字节
指针大小与地址线数量有关,而地址线与系统位数相关
数组中的指针
int arr[5] = {0};
arr = &arr[0];
//arr数组名称的指针储存的是数组的首元素的地址
&arr
//&arr取出的是整个数组的地址
&arr与&arr+1相差20字节
arr与arr+1相差4字节
注:在函数中传入的数组为数组首位的指针,故在函数中利用sizeof计算的结果为指针变量的内存大小。
指针的类型
理解:指针的类型决定指针向前或后走一步的距离
void main()
{
int arr[] = { 0 };
int* p = arr;
char* cp = arr;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("%p\n", cp);
printf("%p\n", cp + 1);
}
//输出结果第一行地址与第二行地址相差4
//输出结果第三行地址与第四行地址相差1
example:
void main()
{
int arr[] = {1,2,3,4,5};
short* p = (short*) arr;
for (int i = 0; i < 4; ++i)
{
*(p + i) = 0;
}
//由于p是short类型的指针变量,所以移动单位为2字节
for (int i = 0; i < 5; ++i)
{
printf("%d ", *(arr + i));
}
}
//输出结果为0 0 3 4 5
万能指针(void*)
void*
可以接受任何类型的指针
(3条消息) 类qsort的多用冒泡排序函数_HS-Cai的博客-CSDN博客https://blog.csdn.net/qq947467490/article/details/128068613
避免野指针
1.对指针初始化
2.避免指针越界
3.指针指向空间释放即使置NULL
int* p = NULL;
//当不知道p该初始化什么地址的时候,直接初始化为NULL
二级指针
创建与初始化
指针数组传参使用二级指针
void test2(int** arr2)
{}
void main()
{
int* arr2[10] = { 0 };
test2(arr2);
}
指针数组的名称为首元素的指针的地址
三、结构体
单独结构标记的声明
#include<stdio.h>
//单独声明结构标记
struct BOOK
{
char name[20];
//创建成员变量
int page;
float price;
};
void main()
{
struct BOOK s = { "Rich Dad Poor Dad",351,48.00 };
//创建并初始化结构体变量s
printf("%s %d %.2f$", s.name, s.page, s.price);
//对结构体变量s中的成员变量进行打印
}
如果加上指针的运用可以写成
#include<stdio.h>
struct BOOK
{
char name[20];
int page;
float price;
};
void main()
{
struct BOOK s = { "Rich Dad Poor Dad",351,48.00 };
struct BOOK* p = &s;
//此处应注意数据类型,来创建指针变量
printf("%s %d %.2f$", p->name, p->page, p->price);
}
如果先创建结构体变量后格式化可以写成
#include<stdio.h>
struct BOOK
{
char* name;
int page;
float price;
};
void main()
{
struct BOOK s1;
s1.name = "RICH";
s1.page = 111;
s1.price = 15.80;
printf("%s\n%d\n%.2f$", s1.name, s1.page, s1.price);
}
[重点]注:如此定义结构体必须要使用指针来创建字符串
结构标记的声明与结构变量的定义一起
#include<stdio.h>
struct BOOK
{
char name[20];
int page;
float price;
}s1 = { "Rich Dad Poor Dad",351,48.00 }, s2;
//同时声明了结构体变量s1,s2
//此时s1,s2为全局变量
void main()
{
printf("%s\n%d\n%.2f$", s1.name, s1.page, s1.price);
}
结构类型的定义
#include<stdio.h>
//利用typefed函数将struct BOOK重定义为book
typedef struct BOOK
{
char name[20];
int page;
float price;
}book;
void main()
{
book s1 = { "Rich Dad Poor Dad",351,48.00 };
printf("%s\n%d\n%.2f$", s1.name, s1.page, s1.price);
}
*结构体嵌套使用
//嵌套结构体的使用
#include<stdio.h>
struct publication
{
int year;
int month;
int day;
};
struct BOOK
{
struct publication c;
char name[20];
int page;
float price;
};
void main()
{
struct BOOK s1 = { {1997,11,14} , "Rich Dad Poor Dad" , 351 , 48.00 };
printf("%d.%d.%d\n%s\n%d\n%.2f$", s1.c.year, s1.c.month, s1.c.day, s1.name, s1.page, s1.price);
}
注:嵌套使用另一个结构体需要,在主结构体中创建所嵌套使用的结构体变量。
*结构体数组
#include<stdio.h>
struct book
{
char* name;
int page;
float price;
};
void print(struct book* p)
{
printf("%s", p->name);
}
void main()
{
struct book s[3] = { {"POOR", 168, 28.00} ,{"RICH", 172, 25.00} ,{"WINNER", 180, 24.00} };
print(s + 1);
}
结构体的传参选择
函数传参的时候,参数是需要压栈的。如果传递—个结构体对象的时候,结构体过大,参数压栈的时候系统开销会比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。
example:
将结构体变量b的地址传给函数print
#include<stdio.h>
struct publication
{
int year;
int month;
int day;
};
struct BOOK
{
struct publication s2;
char* id;
int page;
float price;
};
void print(struct BOOK* b)
{
printf("%d.%d.%d\n%s\n%d\n%.2f\n", b->s2.year, b->s2.month, b->s2.day, b->id, b->page, b->price);
}
void main()
{
struct BOOK b = { {1997,11,14},"POOR OR RICH",197,25.00 };
print(&b);
}
*结构体大小计算
1、找到结构体中最大的成员变量所占的字节数
2、结构体中成员变量的起始地址为该成员变量大小的整数倍(一般情况下我们认为第一个成员变量的起始地址为0)
3、结构体变量的总大小必须要是结构体中最大变量字节的整数倍(不足补齐)
example:
struct demo{
short a;
double b;
int c;
}demo;
a从0开始占2个字节,因为b为8个字节所以起始地址必须从8开始到16,c变量起始地址从16开始到20,总共20个字节,但是因为结构体变量的总大小必须为8字节的倍数,所以补齐为24