【C/C++】内存基础
1. 基本数据类型
short s = 0x4142; // 16进制 char c = *(char*)&s; cout << c << endl;
我的电脑上输出为字符 'B'
Why???
short 型在内存中占 2 字节(bytes),bit 表示如下。
&s 取 s 的地址,(char *)&s 让机器强行认为该指针指向 char 型元素,而 char 型在内存中占 1 字节。
因此用 * 取地址后只向后获取 8 bits(1 字节),得到 0x41 赋值给 char 型变量 c。
但 0x41 = 65,这样 char c 应该是字符 'A' 啊???
因为计算机系统以字节(8 位)为单位,对于位数大于 8 位的处理器,例如 16 位或 32 位处理器,由于寄存器宽度大于 1 字节,那么必然存在着安排多个字节的问题。因此就导致了大端存储模式和小端存储模式,俗称大尾 / 小尾。
大尾,是指数据的低位(权值较小的后面那几位)保存在内存的高地址(图示中从左到右即为内存地址从低到高)中;而数据的高位,保存在内存的低地址中。
而我的电脑是小尾处理器,即 short s 的低位 0x42 保存在内存的低地址,高位 0x41 保存在内存的高地址。也就是说,
((char*)&s)[0] = 0x42 // 低地址单元
((char*)&s)[1] = 0x41 // 高地址单元
因此我的电脑将 0x42 赋值给 c,因此输出 'B'。
2. 结构体
struct st {int a;int b; };st s; s.a = 1; s.b = 2; cout << s.b << endl; ((st*)&(s.b))->a = 99; // ((st*)&(s.b))->b = 99; // 很可能报错,操作不合法内存 cout << s.b << endl;
输出为
2
99
Why 99???
而 ((st*)&(s.b))->b 这片内存很可能未开辟,修改该处的值很可能报错。
结构体的大小(sizeof)需要考虑存储结构体变量时的地址对齐问题。
例如以下 2 个结构体
struct Struct1 {char a;int n;char b; };struct Struct2 {char a;char b;int n; };
用 sizeof() 求这两个结构体大小,Struct1 是 12,Struct2 是 8,WHY???
结构体变量大小 = 最后一个成员变量的地址 + 最后一个成员变量的大小。
Struct1 中第一个成员变量的地址就是结构体变量的首地址,第一个成员 char a 的偏移地址为 0;第二个成员 int n 的地址是第一个成员的地址加上第一个成员的大小(0 + 1),其值为 1;第三个成员 char b 的地址是第二个成员的地址加上第二个成员的大小(1 + 4),其值为 5。
然而,实际存储结构体变量时地址要求对齐,编译器在编译程序时有自己的规则,大多编译器会遵循以下两条原则:
(1)结构体中成员变量的偏移地址 = 其自身大小的整数倍
(2)结构体变量大小 = 所有成员变量大小的整数倍,也即所有成员变量大小的公倍数
上例中 Struct1 的第二个成员变量 int n 的偏移地址为 1,并不是自身大小(4)的整数倍,因此编译器在处理时会在第一个成员后面补上 3 个空字节,使得第二个成员的偏移地址变成 4;这样第三个成员 char b 的偏移地址就是(4 + 4),其值为 8;最后结构体变量的大小等于最后一个成员的偏移地址加上其大小(8 + 1),其值为 9,不是所有成员变量大小的整数倍(9 不是 4 的整数倍),因此编译器会在第三个成员变量 char b 后面补上 3 个空字节,结构体总大小即为 12,满足要求。Struct2 也是同理,会在第二个成员 char b 后补 2 个空字节以满足要求。
通过输出这两个结构体每个成员的地址,会更加清楚这种地址结构。
int main() {Struct1 s1;Struct2 s2;cout << sizeof(s1) << " " << sizeof(s2) << endl;cout << "Struct1:" << (void*)&s1 << endl<< "a: " << (void*)&(s1.a) << endl<< "n: " << (void*)&(s1.n) << endl<< "b: " << (void*)&(s1.b) << endl;cout << "Struct2:" << (void*)&s2 << endl<< "a: " << (void*)&(s2.a) << endl<< "n: " << (void*)&(s2.b) << endl<< "b: " << (void*)&(s2.n) << endl;return 0; }
输出结果为
嵌套结构体的大小(sizeof)需要将其展开考虑。 原则如下:
(1)展开后的结构体的第一个成员变量的偏移地址 = 被展开的结构体中最大的成员变量大小的整数倍。
(2)整个结构体变量大小 = 所有成员大小的整数倍(所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体)。
3. Union
Union 是一种特殊的结构体。它能够包含访问权限(默认访问权限是 public)、成员变量、成员函数(可以包含构造函数和析构函数),但不能包含虚函数、静态数据变量、引用(后两者无法共享内存),也不能被用作其他类的基类,它本身也不能从某个基类派生而来。
Union 类型的成员之间是共享内存的,同一时刻,一个 Union 中只有一个值是有效的。因此当多种数据类型要占用同一片内存时,即“n 选 1”时,可以使用 Union 来发挥其长处。
如下例 Union
union Union {struct StructInUnion {int a;int b;}s;int c;int d; };
观察对不同成员赋值造成的相互影响(s、a、c、d 的首地址相同)
1 int main() { 2 Union u; 3 u.s.a = 1; 4 u.s.b = 2; 5 cout << "a: " << u.s.a << endl 6 << "b: " << u.s.b << endl 7 << "c: " << u.c << endl 8 << "d: " << u.d << endl; 9 cout << "----------" << endl; 10 u.c = 3; 11 cout << "a: " << u.s.a << endl 12 << "b: " << u.s.b << endl 13 << "c: " << u.c << endl 14 << "d: " << u.d << endl; 15 cout << "----------" << endl; 16 u.d = 4; 17 cout << "a: " << u.s.a << endl 18 << "b: " << u.s.b << endl 19 << "c: " << u.c << endl 20 << "d: " << u.d << endl; 21 return 0; 22 }
输出结果
3. 数组
主要地,array + k = &array[k],数组即是数组首元素的地址。
类似地,可以通过 k 合法 / 不合法地操作内存。
4. swap() 函数
实现交换两个元素内容的函数swap,对于不同数据类型,C++可使用模板 template,使用模板更加的类型安全(type safe)。
但其实,当编译器发现模板函数被使用(注意,不是被定义),则在编译这段代码时会使用那个强类型构造一个新函数,导致代码膨胀,因此编译效率并不高。
此时可以通过无类型指针 void* 实现泛型编程,优点是执行速度很快,只需要一份代码副本。
但是,
void swap(void *vp1, void *vp2) { void temp = *vp1; *vp1 = *vp2; *vp2 = temp; }
这样写是错误的!!!因为
- 变量无法声明为 void 类型
- void* 无法被解引用,因为系统没有此地址指向的对象的大小信息
要想实现泛型函数,需要在调用的地方传入相关要交换的对象的地址空间大小 size。
void swap(void *vp1, void *vp2, int size) {char *buffer = new char[size];memcpy(buffer, vp1, size);memcpy(vp1, vp2, size);memcpy(vp2, buffer, size);delete[] buffer; }
具体使用如下
int x = 11, y = 22; cout << "x = " << x << ", y = " << y << endl; swap(&x, &y, sizeof(int)); cout << "x = " << x << ", y = " << y << endl; char *a = strdup("storm"), *b = strdup("nevermore"); cout << "a = " << a << ", b = " << b << endl; swap(&a, &b, sizeof(char **)); // 字符串不等长,需要交换指针 cout << "a = " << a << ", b = " << b << endl; swap(a, b, sizeof(char *)); // 错误!!!注意与上面的区别 cout << "a = " << a << ", b = " << b << endl;
注意输出结果的区别
注意字符串的交换传递的参数是二级指针 &a 和 &b,如图
如果直接将一级指针 a、b 作为参数传递,则 swap 函数执行过程中 vp1 的内容是字符串 “storm\0” 的地址,mencpy 后缓冲区 buffer 指向的内容的将是字符串 “stor”(指针变量一律占 4 字节),因此最后交换的是两个字符串的前 4 个字节。
PS:数组名 a、b 即是一级指针,a = &a[0] 隐式传入 &。
5. 线性搜索函数
int 型版本
int lsearch(int key, int array[], int size) {for (int i = 0; i < size; i++)if (array[i] == key)return i;return -1; }
基本数据类型数组的泛型线性搜索函数的实现
// key为搜索值的指针,base为基数组的指针,n为数组元素个数,elemSize为每个元素的大小 void* lsearch(void *key, void *base, int n, int elemSize) {for (int i = 0; i < n; ++i) {void *elemAddr = (char*)base + i * elemSize;if (memcmp(key, elemAddr, elemSize) == 0)return elemAddr;}return NULL; }
TIP: 代码第 3 行,将基数组的首地址强制转换为 char 型指针。WHY???
对于非 void 指针,如 int array[10];
array[i] 等价于 *(array + i),编译器会根据数组类型在 i 后面乘以 sizeof(int),以获取正确的内存地址。
而不允许对 void 指针进行算术运算,是因为编译器不知道 void 数组中每个元素的大小,仅仅使用 base + i 的话编译器并不知道 base 数组中每个元素大小为 elemSize。
这里的强制类型转换利用了 char 型大小为 1 字节的特性,使 elemAddr 指向此 void 数组的第 i 个元素的首地址。
使用 memcmp 可以比较 char、int、double 等基本数据类型,但无法比较字符指针 char * 类型,因为 char * 所指向的字符串不等长, memcmp 的第三个参数无法确定。也就无法在 char ** 字符指针数组中找到所要的 char * 字符串。
因此可以通过函数指针传入我们自定义的比较函数。
// 为char**定制的比较函数 int StrCmp(void *vp1, void *vp2) {char *s1 = *(char **)vp1;char *s2 = *(char **)vp2;return strcmp(s1, s2); }void* lsearch(void *key, void *base, int n, int elemSize, int (*cmpfn)(void *, void *)) {for (int i = 0; i < n; i++) {void *elemAddr = (char *)base + i * elemSize;if (cmpfn(key, elemAddr) == 0)return elemAddr;}return NULL; }
函数的使用如下
int main() {char *heroes[] = {"JUGG", "QOP", "Storm Spirit", "Zeus", "SF"};char *a1 = "JUGG", *a2 = "yyf";char **found;found = (char **)lsearch(&a1, heroes, 5, sizeof(char *), StrCmp);if (found)cout << *found << " is Found!" << endl;elsecout << "Not Found!" << endl;found = (char **)lsearch(&a2, heroes, 5, sizeof(char *), StrCmp);if (found)cout << *found << " is Found!" << endl;elsecout << "Not Found!" << endl;return 0; }
输出为
JUGG is Found!
Not Found!
注意:
- heroes 的类型是 char** ,即字符指针数组,数组元素都是指向一个字符串的指针,这些字符串不在堆(heap)中,它们是字符串常量,存储在静态存储区。(C语言中没有字符串变量,只能用字符数组 char [] 表示)
- 由于 base 是字符串指针数组,即 char**,所以返回值 elemAddr 也是 char** 类型,所以将 found 设置为 char** 类型。当 found 不为空,对 found 解引用后,输出的 *found 为字符指针 char* 类型,指向所要找的字符串。
- 为什么给 char** 定制的比较函数 StrCmp 要这样写???
- 我们逻辑上知道 elemAddr 是 char** 类型,并且我们希望对 vp1 和 vp2 的处理保持格式一致,因此我们传递的参数key,即 &a1 和 &a2 也是 char** 类型。因此把 vp1 和 vp2 强制类型转换为 char**,这样在逻辑上正确。
- 我们继续将 vp1 和 vp2 解引用,是因为这样得到的 s1 和 s2 是 char* 类型,对字符指针指向的字符串比较可以调用内置函数 strcmp()。
以上。
转载于:https://www.cnblogs.com/wayne793377164/p/8796471.html
【C/C++】内存基础相关推荐
- Linux系统诊断实践-内存基础
简介:Linux系统诊断-内存基础 1. 背景 谈及linux内存,很多时候,我们会关注free,top等基础命令.当系统遇到异常情况时,内存问题的根因追溯,现场诊断时,缺乏深层次的debug能力.本 ...
- Linux系统诊断-内存基础
简介: Linux系统诊断-内存基础 1. 背景 谈及linux内存,很多时候,我们会关注free,top等基础命令.当系统遇到异常情况时,内存问题的根因追溯,现场诊断时,缺乏深层次的debug能力. ...
- 计算机内存知识txt,计算机内存基础知识专题
计算机内存基础知识专题 计算机是由哪几部分组成的呢?简单的说,一个完整的计算机系统是由软件和硬件组成的.其中,硬件部分由中央处理单元(运算器和控制器).存储器和输入/输出设备构成.这次我们要谈的是存储 ...
- 【操作系统复习】ch3 内存基础
内存基础 内存存储数据与指令,如果想让数据交给cpu进行处理,需要将数据放入内存. 内存一般按字/字节进行编址 指令一般包含操作码以及若干参数,有些参数是存储在内存当中的,表明的就是内存的地址 逻辑地 ...
- 【STM32H7教程】第25章 STM32H7的TCM,SRAM等五块内存基础知识
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第25章 STM32H7的TCM,SRAM ...
- 内存基础知识汇总指南
什么是内存 序论 今天,不管你的计算机有多少内存,内存总像是不太够用.不久前,个人计算机能够拥有超过1或2MB的内存还是闻所未闻的,而今天,大多数的系统需要64MB以执行基本的应用程式,且需要至少25 ...
- (王道408考研操作系统)第三章内存管理-第一节1:内存基础知识、程序编译运行原理
注意:本节内容和<计算机组成原理>"存储器"那一部分内容相关性很强,组成原理是从硬件角度,操作系统是从软件角度.因此如果没有学习过这部分的同学,强烈建议先看这一部分 王 ...
- linux性能评估-内存基础理解篇
1.Linux内存是怎么工作的 1.内存映射: 2.虚拟内存空间分布 3.内存分配与回收 4.如何查看内存使用情况 小结 2.理解内存中的Buffer和Cache? 1.free 数据的来源 2.pr ...
- DDR 内存基础知识(2)- DDR预取
对预取概念理解之前,先来复习下DDR颗粒位宽的概念.上一篇文章说了,一个Memory Array中由行地址和列地址的交叉选中一个位,若两个Array叠加在一起,就同时选中了两个Bit,位宽是X2.若四 ...
- android图片压缩之图片和内存基础
1.堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的.堆的大小不是一成不变的,通常有一个分配机制来控制它的大小.比如初始的HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8 ...
最新文章
- Java Socket通信编程
- java程序设计基础29_java程序设计基础实验29
- OJ1306-最长公共子上升序列【dp】
- Linux 实现网页劫持,Linux下实现劫持系统调用的总结(上)--代码及实现
- python库迁移到没有网的电脑_python实现数据库跨服务器迁移
- SQL Server数据库-限制返回行数
- vonic 环境配置_Vonic 2.0 全新文档站上线
- android自定义对话框_Android自定义提醒对话框
- custom transition animation between UIViewControllers
- 计算机模拟技术在材料中的应用,浅谈计算机模拟技术在材料科学中的应用.doc...
- 机器学习时会发生什么
- 乐高ev3python教程_入门篇丨使用EV3机器人,趣味学习Python编程语言~
- css宋体代码_css怎么设置字体为宋体
- 大调查:2018中国程序员真实薪资曝光,看看你达到平均水平了吗?
- 01-Chrome架构:仅仅打开了1个页面,为什么有4个进程
- 安卓系统启动脚本init.rc说明文件readme.txt翻译
- Qt官方示例-正则测试工具
- 纯千兆电口和自适应电口的区别
- PAT乙级|C语言|1025 反转链表 (25分)
- 在那刹那间,俺的世界一片灰暗~~~