关于指针和堆栈

转载:http://blog.qdac.cc/?p=2804

【码神】[长春]swish(109867294) 21:17:40

这块要明白一个东西,我们程序所能操作的数据,从CPU的角度来看,只是在寄存器中的那几个东西。剩下的内存中的东西,磁盘上的东西,实际上对于CPU核心来说,都是外部的东西。

【码神】[长春]swish(109867294) 21:19:07
就象对一个人来说,你大脑所能操纵的是你自己的身体和四肢,通过你的身躯和语言去操纵身边的人和物

【码帝】【宁波】空(9534557) 21:19:09
变量是地址的别称啊

【码神】[长春]swish(109867294) 21:19:32
对的,对于CPU里说,内存也不是它能直接操纵的

【码神】[长春]swish(109867294) 21:20:24
所以,这就存在一个问题,我们要操纵一块内存怎么办?

【码神】[长春]swish(109867294) 21:21:54
我们要使唤一个人,首先就要知道这个人在那儿,你连这个人都找不到,你想操作他,那就是属于白日做梦了。同理你操纵一块内存时,当然也要知道这块内存在那儿,这样子才有机会去操纵它。

【码神】[深圳]音儿小白(2514718952) 21:22:08
@[长春]swish 所以我们看反汇编, 有一堆的 mov eax, [xxxx]

【码神】[深圳]音儿小白(2514718952) 21:22:44
内存地址都必须放到寄存器后,才能进行运算

【码神】[长春]swish(109867294) 21:23:33
我们用来标明内存块的位置,就是地址。用来存放这个地址的变量,在编程语言里就叫做指针。

【码神】[长春]swish(109867294) 21:24:17
这样子说大家应该明白了,指针就是在编程语言中用于存贮一块内存的地址的变量。

【码神】[长春]swish(109867294) 21:25:11
那么问题来了,这个变量本身它也需要占用内存,所以,它也会有自己的地址。这个地址它又存在那里?

【码神】[长春]swish(109867294) 21:27:15
我们声明一个指针类型的变量,99%的情况是存在栈里,而剩下的1%是存在堆

【码神】[长春]swish(109867294) 21:27:52
比如:
var
p:Pointer;

【码神】[长春]swish(109867294) 21:28:15

这个p本身的地址是存在栈里的,而不是在堆里。

【码神】[长春]swish(109867294) 21:29:26
但是好玩的是指针里存的地址,99%却是堆里的

【码神】[长春]swish(109867294) 21:31:16
咱们以一个简单的例子来说明指针这个事:
var
S:UnicodeString;
begin
SetLength(S,0);
SetLength(S,100);
S:=’abc';
S:=’def';
end;

【码神】[长春]swish(109867294) 21:31:47
这个例子很简单,UnicodeString 是 XE 以后默认的 String 对应的类型

【码神】[长春]swish(109867294) 21:32:24
这里注意一个问题,String 并不是一个简单类型

【码妖】[青岛] 阿木(345148965) 21:33:05
string是一个自己生命周期的特殊对象

【码神】[长春]swish(109867294) 21:33:11
为了了解 String 这个类型的本质,我们将其对应的 C++ 定义挪过来

【码神】[长春]swish(109867294) 21:34:34
我们捡核心的来
【码神】[长春]swish(109867294) 21:34:35
#pragma pack(push,1)
struct StrRec {
#ifdef _WIN64
int _Padding;
#endif /* _WIN64 */
unsigned short codePage;
unsigned short elemSize;
int refCnt;
int length;
};
#pragma pack(pop)

const StrRec& GetRec() const;
StrRec& GetRec();

private:
WideChar *Data;
【码神】[长春]swish(109867294) 21:35:12
这是它的定义,可以看到它是一个 StrRec 记录+一个数据指针的形式

【码神】[长春]swish(109867294) 21:35:27
现在回到例子:
var
S:UnicodeString;

【码神】[长春]swish(109867294) 21:35:39
我们讨论下这发生了什么?

【码神】[深圳]音儿小白(2514718952) 21:36:47
在栈上建立了一个StrRec

【码神】[长春]swish(109867294) 21:36:52
首先,由于在栈上分配内存,所以 S 的地址就是当前栈顶的地址,然后将这个地址就会当成是变量S的地址,我们 @S 时得到的就是这个地址。

【码神】[长春]swish(109867294) 21:37:19
音儿落下了Data 成员

【码神】[长春]swish(109867294) 21:37:58
是 ESP 的值增加了至少 StrRec+Data 这个值

【码神】[深圳]音儿小白(2514718952) 21:38:08

【码神】[长春]swish(109867294) 21:38:46
@S 这个得到的值如果我们赋给一个变量,这个变量就是一个指针类型的变量

【码神】[长春]swish(109867294) 21:39:09
接着再往下走,看看 SetLength(S,0) 会发生什么?

【码神】[长春]swish(109867294) 21:39:43
实际上,UnicodeString 做为 Delphi 的内部类型,享受了许多高级待遇。

【码神】[长春]swish(109867294) 21:40:07
1、它不象普通的记录,它会被初始化
2、它的值会自动清理;
3、它基于引用计数来管理 Data 成员的释放;

【码神】[长春]swish(109867294) 21:41:58
现在实际上在 SetLength 之前,它已经象音儿说的,初始化原来的ESP地址到新的 ESP 地址之间的栈上的内存块的内容为0。

【码神】[长春]swish(109867294) 21:42:53
然后调用 SetLength 时,它会比较StrRec.Length与你需求的Length的长度,如果一样就啥也不干

【码神】[长春]swish(109867294) 21:45:13
SetLength(S,100)
这个做的事情就是比较长度,然后用 GetMem 分配100字节的内存,并把它的地址保存到 Data 成员上,这个Data 就是指向这块新内存的指针了,原来是指向空地址的,现在有新地址了。

【码神】[长春]swish(109867294) 21:46:41
我们跑到下一步:
S:=’abc';
这个发生什么?

【码神】[长春]swish(109867294) 21:49:22
abc 首先是一个字符串常量,这个东西会首先会在内存的某个不起眼的角落里呆着

【码神】[长春]swish(109867294) 21:49:57
这个实际上发生了好几件事

【码神】[长春]swish(109867294) 21:53:23
第一句是将 S 的地址存到eax里,第二步是将’abc’这个常量在内存中的地址给放到edx里

然后调用UStrLAsg 这个函数来完成实际的赋值过程

【码神】[长春]swish(109867294) 21:54:12
procedure _UStrLAsg(var Dest: UnicodeString; const Source: UnicodeString); // locals
var
P: Pointer;
begin
if Pointer(Source) <> nil then
_UStrAddRef(Pointer(Source));
P := Pointer(Dest);
Pointer(Dest) := Pointer(Source);
_UStrClr(P);
end;

【码神】[长春]swish(109867294) 21:54:29
好了,这个函数的源码咱们是可以看到的

【码神】[长春]swish(109867294) 21:56:23

翻译上面的代码就是:如果源不为空,则将源的引用计数+1,然后将源的地址保存到目标地址上去

【码神】[长春]swish(109867294) 21:57:36
但你注意到它的参数了吧,’abc’已经是一个UnicodeString了,所以实际上你定义的字符串常量已经在启动时变成UnicodeString了

【码神】[长春]swish(109867294) 21:57:50

注意一点,WideString 没有引用计数

【码神】[长春]swish(109867294) 21:59:40
所以实际上做的事情咱们完整说下来是:
1、将 abc 通过空所说的Move方式保存起来,此时其引用计数为1;
2、将这个字符串中数据的地址赋给目标变量s,同时增加引用计数;
3、清理掉原来的内存块;

【码神】[长春]swish(109867294) 22:01:01
好了,现在咱们再扯回指针。

【码神】[长春]swish(109867294) 22:01:41
看看都发生了啥变化,都有什么东西游来游去。

【码神】[长春]swish(109867294) 22:02:11
在整个过程中,S 这个变量所在的栈的位置是不变的,所以 @S 的值就是固定的

【码神】[长春]swish(109867294) 22:02:43
无论你对 S 赋值还是删除元素,@S 永远和你函数刚进来时一样。

【码神】[长春]swish(109867294) 22:04:02
同样,由于@S 这个地址到 @S+SizeOf(S) 这段地址中各个元素的地址也是不变的,也就是说,Data 这个指针的自身的地址也是不变的,变的只是 Data 这个地址存的内容

【码神】[长春]swish(109867294) 22:07:26
咱们简化下,假设 S 在栈上的地址是0,Data 本身的地址咱不算了,随便指定一个,假设是16,初始化完成后,这个内存块是这样子的:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
此时,Data 的内容从16-19这四个字节的内容是 0 0 0 0
当我们用SetLength或者赋值等等方式,Data 保存了新的地址,假设这个地址是 1,上面的内容就会变为
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0

【码神】[长春]swish(109867294) 22:07:55
之所以1在前面是因Windows 是LE 编码,小头在前,这个简单说一下

【码神】[长春]swish(109867294) 22:08:28
此时,@Data 的地址不变,Data 的内容变了,变成了1

【码神】[长春]swish(109867294) 22:09:51
现在继续,既然指针指向的是一个地址,那么,根据一个萝卜一个坑的原则,通过指针,你访问数据时,永远只能直接访问的是当前元素

【码神】[长春]swish(109867294) 22:10:36
但拨萝卜可以一个个的拨,所以,地址可以一个个的往下跳,也可以往前跳

【码神】[长春]swish(109867294) 22:12:56
说到这里,我们就要了解跳坑的规则

【码妖】[青岛] 阿木(345148965) 22:13:00
那data的内容是在堆还是栈?

【码帝】【宁波】空(9534557) 22:13:11
@[青岛] 阿木 堆

【码神】[长春]swish(109867294) 22:13:55
指针类型的跳坑原则就是只能整数倍跳,不能半个半个跳

【码妖】[浙江]空号WaxBerray(286195153) 22:14:30
堆最大能分配多大?

【码神】[长春]swish(109867294) 22:15:13
如果不考虑32位和64位程序的区分,堆能分配的大小受物理可用内存限制

【码神】[长春]swish(109867294) 22:15:34
这个咱们以前讨论过

【码妖】[浙江]空号WaxBerray(286195153) 22:15:34
那栈呢?

【码神】[长春]swish(109867294) 22:15:45
栈默认是1MB

【码神】[长春]swish(109867294) 22:15:57
可以在项目设置里修改

【码神】[长春]swish(109867294) 22:16:14
咱先继续,一会再讨论堆和栈的问题

【码神】[长春]swish(109867294) 22:17:16
比如一个32整数类型的指针 P,当你加1时,它跳的是4个字节,当你减1时,它跳的是-4个字节,而不会是其它值

【码神】[长春]swish(109867294) 22:17:31
同样,一个PByte类型的指针,就是一个一个字节的跳了

【码神】[长春]swish(109867294) 22:18:18
它等价于:
P:=Integer(P)+SizeOf(TypeOfP);

【码神】[长春]swish(109867294) 22:20:26
正是因为指针类型跳的长度是固定的,而指针指向地址对应的内存块是连续的(99.9%的情况),所以你可以一次跳多个坑,当然跳多了越过界了,掉下去就是你自己的事了。

【码神】[长春]swish(109867294) 22:20:36
这也是指针使用的风险所在

【码妖】[浙江]空号WaxBerray(286195153) 22:21:39
那怎么避免这个风险呢?

【码神】[长春]swish(109867294) 22:21:50
因为指针可以随便的跳来跳去,在 C++ 中,将它归为了随机迭代器。关于随机迭代器的概念,咱也不拓展了,知道这么个事就好。

【码神】[长春]swish(109867294) 22:22:11
将来有兴趣学C++ 的STL 的时候,会了解到这些。

【码神】[深圳]音儿小白(2514718952) 22:22:26
判断是不是超过上限

【码神】[深圳]音儿小白(2514718952) 22:22:31
P + Len

【码神】[长春]swish(109867294) 22:22:39
这个风险就是靠程序员来控制的,编译器无法替你控制

【码神】[长春]swish(109867294) 22:23:54
越界的话,最典型的是AV错误

【码神】[长春]swish(109867294) 22:24:39
但注意不是100%出,不能说越界就一定出

【码神】[长春]swish(109867294) 22:24:59
现在咱们的指针的说明基本也差不多了

【码神】[长春]swish(109867294) 22:25:10
回来再把堆和栈是怎么回事简单说下

【码神】[长春]swish(109867294) 22:26:23
栈上的内存是预分配的,不管你用不用,它都在那儿占着

【码妖】[浙江]空号WaxBerray(286195153) 22:26:58
为什么要这么设计栈?

【码神】[长春]swish(109867294) 22:27:09
堆上的内存是按需分配,只有用到时,你才会去申请,操作系统给你分配,用完,你需要负责将内存还回去的

【码神】[长春]swish(109867294) 22:27:44
为什么这么设计又是一个长篇大论了,简而言之的话,是为了方便和性能

【码神】[长春]swish(109867294) 22:28:57
因为是连续预分配的,所以你声明一个新的变量时,几乎是没有什么额外的开销,所唯一要做的,就是调整下ESP的值,内存分配就算完事了,释放的时候,再调整下ESP的值,内存释放也就算完事了,当然这是说简单类型。

【码神】[长春]swish(109867294) 22:29:36
而堆的话,内存分配因为是随用随申请,所以它的大小理论上仅受物理内存中的连续内存块大小限制。

【码神】[长春]swish(109867294) 22:30:09
堆上的内存申请理论上的上限是你物理内存中连续的最大块内存的大小。

【码帝】【宁波】空(9534557) 22:30:36
@[深圳]音儿小白 这个就是为什么这个月上了XE7之后我苦难的地方.纠结很久.

【码神】[长春]swish(109867294) 22:30:54
由于堆上的内存在不停的重复的申请和释放,也就引申出了内存碎片的概念,但不在咱们讨论的范围了。

【码神】[长春]swish(109867294) 22:31:10
先说这么些吧,有什么问题咱们再讨论

问题:UnicodeString 的 Length 是否就是字符数?

【码神】[长春]swish(109867294) 22:34:12
UnicodeString 的字符数和Length并不一定相等

【码神】[深圳]音儿小白(2514718952) 22:34:19
那是什么?

【码帝】【宁波】空(9534557) 22:34:40
不是的. 字符计数不是这样的. 这个我也不确定.因为UNICODE的概念 计数是字数,1个字或许会有2个WIDERCHAR

【码妖】[浙江]空号WaxBerray(286195153) 22:34:46
一直以为是字符数量

【码神】[长春]swish(109867294) 22:35:06
Unicode 有所谓的扩展区的概念

【码神】[长春]swish(109867294) 22:35:15
扩展区的字符1个占4个字节

【码神】[深圳]音儿小白(2514718952) 22:35:20
唉,咱们中文,英文,数字都没错的, 就是字符数

【码神】[长春]swish(109867294) 22:35:24
$DB00~$DFFF

【码神】[深圳]音儿小白(2514718952) 22:35:40
好像别的语言会可能不一样

【码神】[长春]swish(109867294) 22:35:41
音儿,就是中文的时候出的问题

【码神】[长春]swish(109867294) 22:36:02
比如说这个字

【码神】[深圳]音儿小白(2514718952) 22:36:05
中文在unicodestring中, 一个字符会有不是两字节的情况?

【码神】[深圳]音儿小白(2514718952) 22:36:50
群主说说例外?

【码神】[长春]swish(109867294) 22:37:00
这个字估计很多人不认识:

【码神】[长春]swish(109867294) 22:46:23
还有一个提示吧,GBK 里,汉字也是2 或4个字节

【码神】[长春]swish(109867294) 22:46:38
GB2312算做 GBK 的子集

问题2:什么编码最省空间?

最省空间的是GBK编码,这个昨晚因为太晚没有做解释。许多人认为 UTF8 编码最省空间,但实际上不是的,UTF8 和 GBK 处理英文时都是 1 个字节,但处理中文时, GBK 编码要么是2个字节,要么是4个字节(极少数),而 UTF8 编码普遍是 3-4 个字节,综合下来,GBK 编码实际上占用的空间还是要略小。但 GBK 编码的劣势就是它是基于 GB2312 发展过来的,转换为 Unicode 编码时,实际上经过了一个查表转换的过程,通用性也稍差一点。

转载于:https://www.cnblogs.com/h2zZhou/p/4729858.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/419656.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php 文件类型 html,HTML的文档类型怎么选择

声明帮助浏览器正确地显示网页。声明(推荐学习&#xff1a;HTML入门教程)Web 世界中存在许多不同的文档。只有了解文档的类型&#xff0c;浏览器才能正确地显示文档。HTML 也有多个不同的版本&#xff0c;只有完全明白页面中使用的确切 HTML 版本&#xff0c;浏览器才能完全正确…

HDOJ 2037 今年暑假不AC 【贪心】

HDOJ 2037 今年暑假不AC 【贪心】 题目链接 http://acm.hdu.edu.cn/showproblem.php?pid2037 给出n个电视节目的开始时间和结束时间&#xff0c; 要求合理安排如果看这些节目可以完整的看完更多节目 策略是观看的节目的长度越短越好&#xff0c;也即节目越早结束我越想看它。…

php 邮件发送验证码,发送验证码邮件有什么好的解决方案?

用户Passport系统&#xff0c;注册&#xff0c;找回密码都是通过邮件发送验证码&#xff0c;有什么好的方案实现。我之前用的是私人的QQ邮箱&#xff0c;发现偶尔系统会来一封邮件要我输入验证码&#xff0c;而且就怕用多了会被腾讯拉黑或者自动移到垃圾箱处理。还有就是什么情…

自定义多列排序:C++/Java实现

前言&#xff1a; 有些时候&#xff0c;我们在编程中会遇到多列排序的需求。假如在execle,这事儿就太easy了。不过没办法&#xff0c;现在就需要你用Java或者C实现这样一个功能&#xff01; 比如将下表无序的数据通过重排之后按照以下规则显示结果&#xff1a; 1.第二列从大到小…

php的prepare方法,PHP之PDO-prepare

当同一个SQL多次查询(执行)时,只是每次的查询条件(数据)不一样,那么,使用prepare就对了.它可大大减少查询(执行)时间,服务器资源消耗..原型:PDOStatement PDO::prepare(string query [, array driver_options])占位符:1,有名占位符(:named parameters)2,问号占位符(?)如:INSER…

多态,面向对象

/** * 里氏替换原则 : * 能使用父类的地方,一定可以使用子类 * 什么是多态 : * 父类的引用,指向子类的对象 * 多态的前提条件 : * 有继承关系的两个类 * 多态的目的 : * ☆☆☆ 代码的重用 * 多态发什么在什么时候 : * 赋值…

mysql安装im,mysql安装记录

zip下载及安装教程&#xff1a;https://blog.csdn.net/qq_41307443/article/details/79839558我按照步骤操作遇到了一些问题记录一下&#xff1a;1 没有 ini ,文件&#xff0c;自己建立一个新的 .ini文件。自己的系统没显示后缀&#xff0c;我配置了一下&#xff1b;2 启动服务…

python爬取网页表格数据匹配,python爬虫——数据爬取和具体解析

标签&#xff1a;pattern div mat txt 保存 关于 json result with open关于正则表达式的更多用法&#xff0c;可参考链接&#xff1a;https://blog.csdn.net/weixin_40040404/article/details/81027081一、正则表达式&#xff1a;1.常用正则匹配&#xff1a;U…

Notification的学习,4.0前后的差别,和在设置声音的时候获取资源的uri方法

android 4.0 前后很多api都有了较大的差别&#xff0c;不多说现在学习下notification前后实现的差别&#xff0c;本文参考了 &#xff1a;http://my.oschina.net/ososchina/blog/353692&#xff1b;http://gundumw100.iteye.com/blog/1873318&#xff1b; http://blog.csdn.net…

标题 日期问题java蓝桥杯,日期类的使用(java)-蓝桥杯

蓝桥杯日期问题常考&#xff0c;java提供了日期类很方便&#xff1b;//日历类Calendar c Calendar.getInstance();  // 获取实例化对象Date date c.getTime();   // 日期类得到c的时间&#xff1b;SimpleDateFormat sdf new SimpleDateFormat("yyyy-mm-dd hh:mm:s…

前端学习(1598):ref转发

第一种方式 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><script src&…

PHP opencv Dlib,Face_Recognition

Face_Recognition使用Opencv和Dlib实现基于视频的人脸识别文件夹介绍1、Resources\pictures此文件夹下存放人脸保存结果2、Resources\video此文件夹下存放带标注视频保存结果3、Resources\faceS此文件夹下存放各个人物的图片&#xff0c;用于人脸库的建立4、Resources\featureD…

linux c截断文件

http://www.cnblogs.com/zhuxiongfeng/archive/2010/08/24/1807505.html转载于:https://www.cnblogs.com/jingzhishen/p/4745978.html

oracle dbra,资源供给:IO子系统之二

案例描述&#xff1a;某运营商的dbra备份系统&#xff0c;备份构建在vxvm和vxfs文件系统之上&#xff0c;串行更新的速度基本理想。由于无法达到更新目标&#xff0c;通过增加并行来增加IO写速度&#xff0c;结果并行度加大之后&#xff0c;iops快速下跌&#xff0c;io子系统无…

[Android]Android端ORM框架——RapidORM(v1.0)

以下内容为原创&#xff0c;欢迎转载&#xff0c;转载请注明 来自天天博客&#xff1a;http://www.cnblogs.com/tiantianbyconan/p/4748077.html Android上主流的ORM框架有很多&#xff0c;常用的有ORMLite、GreenDao等。 ORMLite&#xff1a; &#xff0d;优点&#xff1a;AP…

oracle代码实例,oracle存储过程代码实例

1、用来插入大量测试数据的存储过程CREATE OR REPLACE PROCEDURE INSERTAMOUNTTEST(ST_NUM IN NUMBER,ED_NUM IN NUMBER)ISBEGINdeclarei number;beginFOR i IN ST_NUM..ED_NUM LOOPINSERT INTO tb values(i,i,3,3,3,100,0);END LOOP;end;END;运行&…

Examining Open vSwitch Traffic Patterns

In this post, I want to provide some additional insight on how the use of Open vSwitch (OVS) affects—or doesn’t affect, in some cases—how a Linux host directs traffic through physical interfaces, OVS internal interfaces, and OVS bridges. This is somethi…

Docker 面临的安全隐患,我们该如何应对

【编者按】对比虚拟机&#xff0c;Docker 在体量等方面拥有显著的优势。然而&#xff0c;当 DevOps 享受 Docker 带来扩展性、资源利用率和弹性提升的同时&#xff0c;其所面临的安全隐患同样值得重视&#xff0c;近日 Chris Taschner 在 SEI 上撰文进行了总结。本文系 OneAPM …