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++】内存基础相关推荐

  1. Linux系统诊断实践-内存基础

    简介:Linux系统诊断-内存基础 1. 背景 谈及linux内存,很多时候,我们会关注free,top等基础命令.当系统遇到异常情况时,内存问题的根因追溯,现场诊断时,缺乏深层次的debug能力.本 ...

  2. Linux系统诊断-内存基础

    简介: Linux系统诊断-内存基础 1. 背景 谈及linux内存,很多时候,我们会关注free,top等基础命令.当系统遇到异常情况时,内存问题的根因追溯,现场诊断时,缺乏深层次的debug能力. ...

  3. 计算机内存知识txt,计算机内存基础知识专题

    计算机内存基础知识专题 计算机是由哪几部分组成的呢?简单的说,一个完整的计算机系统是由软件和硬件组成的.其中,硬件部分由中央处理单元(运算器和控制器).存储器和输入/输出设备构成.这次我们要谈的是存储 ...

  4. 【操作系统复习】ch3 内存基础

    内存基础 内存存储数据与指令,如果想让数据交给cpu进行处理,需要将数据放入内存. 内存一般按字/字节进行编址 指令一般包含操作码以及若干参数,有些参数是存储在内存当中的,表明的就是内存的地址 逻辑地 ...

  5. 【STM32H7教程】第25章 STM32H7的TCM,SRAM等五块内存基础知识

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980  第25章       STM32H7的TCM,SRAM ...

  6. 内存基础知识汇总指南

    什么是内存 序论 今天,不管你的计算机有多少内存,内存总像是不太够用.不久前,个人计算机能够拥有超过1或2MB的内存还是闻所未闻的,而今天,大多数的系统需要64MB以执行基本的应用程式,且需要至少25 ...

  7. (王道408考研操作系统)第三章内存管理-第一节1:内存基础知识、程序编译运行原理

    注意:本节内容和<计算机组成原理>"存储器"那一部分内容相关性很强,组成原理是从硬件角度,操作系统是从软件角度.因此如果没有学习过这部分的同学,强烈建议先看这一部分 王 ...

  8. linux性能评估-内存基础理解篇

    1.Linux内存是怎么工作的 1.内存映射: 2.虚拟内存空间分布 3.内存分配与回收 4.如何查看内存使用情况 小结 2.理解内存中的Buffer和Cache? 1.free 数据的来源 2.pr ...

  9. DDR 内存基础知识(2)- DDR预取

    对预取概念理解之前,先来复习下DDR颗粒位宽的概念.上一篇文章说了,一个Memory Array中由行地址和列地址的交叉选中一个位,若两个Array叠加在一起,就同时选中了两个Bit,位宽是X2.若四 ...

  10. android图片压缩之图片和内存基础

    1.堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的.堆的大小不是一成不变的,通常有一个分配机制来控制它的大小.比如初始的HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8 ...

最新文章

  1. Java Socket通信编程
  2. java程序设计基础29_java程序设计基础实验29
  3. OJ1306-最长公共子上升序列【dp】
  4. Linux 实现网页劫持,Linux下实现劫持系统调用的总结(上)--代码及实现
  5. python库迁移到没有网的电脑_python实现数据库跨服务器迁移
  6. SQL Server数据库-限制返回行数
  7. vonic 环境配置_Vonic 2.0 全新文档站上线
  8. android自定义对话框_Android自定义提醒对话框
  9. custom transition animation between UIViewControllers
  10. 计算机模拟技术在材料中的应用,浅谈计算机模拟技术在材料科学中的应用.doc...
  11. 机器学习时会发生什么
  12. 乐高ev3python教程_入门篇丨使用EV3机器人,趣味学习Python编程语言~
  13. css宋体代码_css怎么设置字体为宋体
  14. 大调查:2018中国程序员真实薪资曝光,看看你达到平均水平了吗?
  15. 01-Chrome架构:仅仅打开了1个页面,为什么有4个进程
  16. 安卓系统启动脚本init.rc说明文件readme.txt翻译
  17. Qt官方示例-正则测试工具
  18. 纯千兆电口和自适应电口的区别
  19. PAT乙级|C语言|1025 反转链表 (25分)
  20. 在那刹那间,俺的世界一片灰暗~~~

热门文章

  1. 计算机网络物理层之数字传输系统
  2. HCIE Security 二层攻击防范 备考笔记(幕布)
  3. 华三 h3c Ftp、Telnet配置
  4. 华三 h3c Smart linkMonitor link配置
  5. 深入理解【缺页中断】及FIFO、LRU、OPT这三种置换算法
  6. 005-垃圾收集算法
  7. Git常用的简单命令
  8. PingingLab传世经典系列《CCNA完全配置宝典》-2.9 OSPF基本配置
  9. PHP是迄今为止最好的web平台
  10. 从 2.4 到 2.6:Linux 内核可装载模块机制的改变对设备驱动的影响(二)