指针是一个概念,对于许多人而言,一开始可能会造成混淆,尤其是在围绕周围复制指针值并仍然引用同一内存块的时候。
我发现最好的类比是将指针视为一张纸,上面有一个房屋的地址,而它所引用的内存块就是实际的房屋。因此可以容易地解释各种操作。
我在下面添加了一些Delphi代码,并在适当的地方添加了一些注释。我之所以选择Delphi,是因为我的其他主要编程语言C#不会以相同的方式展现诸如内存泄漏之类的东西。
如果仅希望学习指针的高级概念,则应忽略下面说明中标有“内存布局”的部分。它们旨在举例说明操作后的存储器外观,但是本质上它们是更底层的。但是,为了准确地解释缓冲区溢出是如何工作的,重要的是添加了这些图。
免责声明:出于所有意图和目的,本解释和示例记忆
布局大大简化了。会有更多的开销和更多的细节
需要知道是否需要低级处理内存。但是,对于
解释内存和指针的意图,它足够准确。
假设下面使用的THouse类如下所示:
type
THouse = class
private
FName : array[0..9] of Char;
public
constructor Create(name: PChar);
end;
初始化house对象时,给构造函数的名称将复制到私有字段FName中。将其定义为固定大小的数组是有原因的。
在内存中,将有一些与房屋分配相关的开销,我将在下面举例说明:
---[ttttNNNNNNNNNN]--- ^ ^ | | | +- the FName array | +- overhead
The "tttt" area is overhead, there will typically be more of this for various types of runtimes and languages, like 8 or 12 bytes. It is imperative that whatever values are stored in this area never gets changed by anything other than the memory allocator or the core system routines, or you risk crashing the program.
Allocate memory
Get an entrepreneur to build your house, and give you the address to the house. In contrast to the real world, memory allocation cannot be told where to allocate, but will find a suitable spot with enough room, and report back the address to the allocated memory.
In other words, the entrepreneur will choose the spot.
THouse.Create('My house');
内存布局:
---[ttttNNNNNNNNNN]--- 1234My house
Keep a variable with the address
Write the address to your new house down on a piece of paper. This paper will serve as your reference to your house. Without this piece of paper, you're lost, and cannot find the house, unless you're already in it.
var
h: THouse;
begin
h := THouse.Create('My house');
...
内存布局:
h v---[ttttNNNNNNNNNN]--- 1234My house
Copy pointer value
Just write the address on a new piece of paper. You now have two pieces of paper that will get you to the same house, not two separate houses. Any attempts to follow the address from one paper and rearrange the furniture at that house will make it seem that the other house has been modified in the same manner, unless you can explicitly detect that it's actually just one house.
Note This is usually the concept that I have the most problem explaining to people, two pointers does not mean two objects or memory blocks.
var
h1, h2: THouse;
begin
h1 := THouse.Create('My house');
h2 := h1; // copies the address, not the house
...
h1 v---[ttttNNNNNNNNNN]--- 1234My house ^ h2
Freeing the memory
Demolish the house. You can then later on reuse the paper for a new address if you so wish, or clear it to forget the address to the house that no longer exists.
var
h: THouse;
begin
h := THouse.Create('My house');
...
h.Free;
h := nil;
在这里,我首先建造房屋,并掌握其地址。然后我对房子做一些事情(使用它,...代码,作为练习留给读者),然后释放它。最后,我从变量中清除地址。
内存布局:
h <--+ v +- before free---[ttttNNNNNNNNNN]--- | 1234My house <--+ h (now points nowhere) <--+ +- after free---------------------- | (note, memory might still xx34My house <--+ contain some data)
Dangling pointers
You tell your entrepreneur to destroy the house, but you forget to erase the address from your piece of paper. When later on you look at the piece of paper, you've forgotten that the house is no longer there, and goes to visit it, with failed results (see also the part about an invalid reference below).
var
h: THouse;
begin
h := THouse.Create('My house');
...
h.Free;
... // forgot to clear h here
h.OpenFrontDoor; // will most likely fail
在调用
h
之后使用
.Free
可能有效,但这纯属幸运。在关键操作过程中,它很可能会在客户位置失败。
h <--+ v +- before free---[ttttNNNNNNNNNN]--- | 1234My house <--+ h <--+ v +- after free---------------------- | xx34My house <--+
As you can see, h still points to the remnants of the data in memory, butsince it might not be complete, using it as before might fail.
Memory leak
You lose the piece of paper and cannot find the house. The house is still standing somewhere though, and when you later on want to construct a new house, you cannot reuse that spot.
var
h: THouse;
begin
h := THouse.Create('My house');
h := THouse.Create('My house'); // uh-oh, what happened to our first house?
...
h.Free;
h := nil;
在这里,我们用新房子的地址覆盖了
h
变量的内容,但是旧房子仍然屹立在某处。编写完这段代码后,您将无法到达那所房子,它将被搁置。换句话说,分配的内存将保持分配状态,直到应用程序关闭为止,这时操作系统会将其拆除。
第一次分配后的内存布局:
h v---[ttttNNNNNNNNNN]--- 1234My house
Memory layout after second allocation:
h v---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN] 1234My house 5678My house
A more common way to get this method is just to forget to free something, instead of overwriting it as above. In Delphi terms, this will occur with the following method:
procedure OpenTheFrontDoorOfANewHouse;
var
h: THouse;
begin
h := THouse.Create('My house');
h.OpenFrontDoor;
// uh-oh, no .Free here, where does the address go?
end;
执行此方法后,在我们的变量中没有房屋的地址存在的地方,但是房屋仍然在那里。
内存布局:
h <--+ v +- before losing pointer---[ttttNNNNNNNNNN]--- | 1234My house <--+ h (now points nowhere) <--+ +- after losing pointer---[ttttNNNNNNNNNN]--- | 1234My house <--+
As you can see, the old data is left intact in memory, and will notbe reused by the memory allocator. The allocator keeps track of whichareas of memory has been used, and will not reuse them unless youfree it.
Freeing the memory but keeping a (now invalid) reference
Demolish the house, erase one of the pieces of paper but you also have another piece of paper with the old address on it, when you go to the address, you won't find a house, but you might find something that resembles the ruins of one.
Perhaps you will even find a house, but it is not the house you were originally given the address to, and thus any attempts to use it as though it belongs to you might fail horribly.
Sometimes you might even find that a neighbouring address has a rather big house set up on it that occupies three address (Main Street 1-3), and your address goes to the middle of the house. Any attempts to treat that part of the large 3-address house as a single small house might also fail horribly.
var
h1, h2: THouse;
begin
h1 := THouse.Create('My house');
h2 := h1; // copies the address, not the house
...
h1.Free;
h1 := nil;
h2.OpenFrontDoor; // uh-oh, what happened to our house?
通过
h1
中的引用,这里的房屋被拆了,虽然
h1
也被清除了,但
h2
仍然有旧的,过时的地址。进入不再站立的房屋可能会或可能不会起作用。
这是上面悬空指针的变体。查看其内存布局。
缓冲区溢出
您将更多的东西搬进了房子,超出了您的承受能力,溢出到邻居的房子或院子里。隔壁房子的主人以后回家时,他会发现各种各样的东西,他会考虑自己的。
这就是我选择固定大小的数组的原因。为了做好准备,假设
由于某种原因,我们分配的第二座房屋将放置在
内存中的第一个。换句话说,第二座房屋的高度会降低
地址比第一个。而且,它们彼此相邻分配。
因此,此代码:
var
h1, h2: THouse;
begin
h1 := THouse.Create('My house');
h2 := THouse.Create('My other house somewhere');
^-----------------------^
longer than 10 characters
0123456789 <-- 10 characters
第一次分配后的内存布局:
h1 v-----------------------[ttttNNNNNNNNNN] 5678My house
Memory layout after second allocation:
h2 h1 v v---[ttttNNNNNNNNNN]----[ttttNNNNNNNNNN] 1234My other house somewhereouse ^---+--^ | +- overwritten
The part that will most often cause crash is when you overwrite important partsof the data you stored that really should not be randomly changed. For instanceit might not be a problem that parts of the name of the h1-house was changed,in terms of crashing the program, but overwriting the overhead of theobject will most likely crash when you try to use the broken object,as will overwriting links that is stored toother objects in the object.
Linked lists
When you follow an address on a piece of paper, you get to a house, and at that house there is another piece of paper with a new address on it, for the next house in the chain, and so on.
var
h1, h2: THouse;
begin
h1 := THouse.Create('Home');
h2 := THouse.Create('Cabin');
h1.NextHouse := h2;
在这里,我们创建了从房屋到小屋的链接。我们可以按照链条进行操作,直到一所房子没有
NextHouse
引用,这意味着它是最后一个。要访问我们所有的房屋,我们可以使用以下代码:
var
h1, h2: THouse;
h: THouse;
begin
h1 := THouse.Create('Home');
h2 := THouse.Create('Cabin');
h1.NextHouse := h2;
...
h := h1;
while h <> nil do
begin
h.LockAllDoors;
h.CloseAllWindows;
h := h.NextHouse;
end;
内存布局(将NextHouse添加为对象中的链接,标记为
下图中的四个LLLL):
h1 h2
v v
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
1234首页+ 5678客舱+
| ^ |
+ -------- + *(无链接)
从根本上讲,什么是内存地址?
从基本的角度来说,内存地址只是一个数字。如果你想起记忆
作为字节的大数组,第一个字节的地址为0,下一个字节
地址1,依此类推。这是简化的,但足够好。
因此,此内存布局:
h1 h2
v v
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
1234我的房子5678我的房子
可能有这两个地址(最左边-是地址0):
h1 = 4
h2 = 23
这意味着我们上面的链接列表实际上可能是这样的:
h1(= 4)h2(= 28)
v v
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
1234首页0028 5678机舱0000
| ^ |
+ -------- + *(无链接)
通常将“无处指向”的地址存储为零地址。
从根本上讲,什么是指针?
指针只是一个保存内存地址的变量。您通常可以要求编程
语言以提供编号,但是大多数编程语言和运行时都试图
隐藏数字下面没有数字的事实,只是因为数字本身没有
真的对您没有任何意义。最好将指针视为黑盒,即。
您并不真正了解或关心它的实际实现方式,只要它
作品。
我是一名优秀的程序员,十分优秀!