#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Person
{
unsigned long age;
char name[20];
};
struct Array
{
struct Person someone;
unsigned long used;
unsigned long size;
};
int main()
{
//pointer to array of structs
struct Array** city;
//creating heap for one struct Array
struct Array* people=malloc(sizeof(struct Array));
city=&people;
//initalizing a person
struct Person Rob;
Rob.age=5;
strcpy(Rob.name,"Robert");
//putting the Rob into the array
people[0].someone=Rob;
//prints Robert
printf("%s\n",people[0].someone.name);
//another struct
struct Person Dave;
Dave.age=19;
strcpy(Dave.name,"Dave");
//creating more space on the heap for people.
people=realloc(people,sizeof(struct Array)*2);
//How do I know that this data is safe in memory from being overwritten?
people[1].someone=Dave;
//prints Dave
printf("%s\n",people[1].someone.name);
//accessing memory on the heap I do not owe?
people[5].someone=Rob;
//prints "Robert" why is this okay? Am I potentially overwriting memory?
printf("%s\n",people[5].someone.name);
return 0;
}
在上面的代码中,我尝试创建一个指向动态结构数组的指针,不确定我是否在该部分成功,但我主要担心的是我使用malloc在数组的堆上创建空间&#39; people 。&#39;稍后在代码中我创建另一个struct Person并使用realloc在堆上为人们创建更多空间。&#39;然后我写了一些记忆,除了我认为我为人们做了空间[5] .someone = Rob;。&#39;这仍然有效,因为我可以访问该内存位置的值。我的问题是为什么这有效?我是否可能通过写入我没有专门为人们定义的内存来覆盖内存?我实际上正确使用malloc和realloc吗?正如我所听到的那样,如果他们在另一篇文章中取得成功,就有可能进行测试。我是C的新手,所以如果我的假设或术语不合适,请纠正我。
答案 0 :(得分:5)
我不是C的专家,甚至不是中级,大部分时间都是用C#编程的,因此可能会出现一些错误。
现代操作系统有一种称为内存管理器的特殊机制。使用该机制,我们可以要求操作系统给我们一些内存。在Windows中,有一个特殊功能 - VirtualAlloc。它是一个非常强大的功能,您可以在MSDN上阅读更多相关信息。
它工作得很好并且给了我们所需的所有内存但是有一点问题 - 它给了我们整个物理页面(4KB)。好吧,实际上这不是一个大问题,你可以像使用malloc分配一样使用这个内存。没有错误。
但这是一个问题,因为如果我们使用VirtualAlloc分配一个10字节的块,它实际上会给我们4096字节的块,因为内存大小向上舍入到页面大小边界。所以VirtualAlloc分配了一个4KB的内存块,但实际上我们只使用了10个字节。剩下的4086已经过去了#34;如果我们创建第二个10字节数组,VirtualAlloc将为我们提供另外4096字节的块,因此两个10字节数组实际上将占用8KB的RAM。
要解决此问题,每个C程序都使用malloc
函数,它是C运行时库的一部分。它使用VirtualAlloc分配一些空间并返回指向它的部分的指针。例如,让我们回到之前的数组。如果我们使用malloc分配10字节数组,运行时库将调用VirtualAlloc来分配一些空间,malloc将返回指向它开头的指针。但是如果我们第二次分配10字节数组,malloc就不会使用VirtualAlloc。相反,它将使用已经分配的页面,我的意思是它的自由空间。在分配了第一个数组之后,我们的内存块中有4086个字节的未使用空间。所以malloc将明智地使用这个空间。在这种情况下(对于第二个数组),它将返回指向"address of chunk" + 10
的指针(这是一个内存地址)。
现在我们可以分配大约400个&#34;十个字节的数组&#34;如果我们使用malloc,它们将只占用4096个字节。使用VirtualAlloc的朴素方式需要400 * 4096 bytes = 1600KB
,与使用malloc的4096字节相比,这是一个相当大的数字。
另一个原因 - 性能,因为VirtualAlloc是一项非常昂贵的操作。但是,如果在分配的块中有可用空间,malloc将执行一些指针数学运算,但如果您没有任何空闲分配空间,它将调用VirtualAlloc。实际上它比我说的要复杂得多,但我认为这足以解释原因。
好的,让我们回答这个问题。您为Array
数组分配内存。我们来计算它的大小:sizeof(Person) = sizeof(long) + sizeof(char[20]) = 4 + 20 = 24 bytes
; sizeof(Array) = sizeof(Person) + 2 * sizeof(long) = 24 + 8 = 32 bytes
。 2个元素的数组将采用32 * 2 = 64字节。所以,正如我之前所说,malloc将调用VirtualAlloc来分配一些内存,它将返回一个4096字节的页面。因此,例如,让我们假设块开头的地址为0.应用程序可以修改0到4096之间的任何字节,因为我们分配了页面,我们不会得到任何页面错误。什么是数组索引array[n]
?它只是数组基数的总和和计算为array + n * sizeof(*array)
的偏移量。如果是person[5]
,则为0 + sizeof(Array) * 5 = 0 + 5 * 64 = 320 bytes
。疑难杂症!我们仍然处于大块的边界,我的意思是我们访问现有的物理页面。如果我们试图访问未存在的虚拟页面,则会发生页面故障,但在我们的情况下,它存在于地址320(我们假设为0到4096)。访问未分配的空间是危险的,因为它可能导致许多未知的后果,但我们实际上可以做到!
这就是为什么你没有得到任何Access Violation at ****
。但它实际上很多。因为如果您尝试访问零指针,您将收到页面错误,您的应用程序将崩溃,因此您将在调试器或其他东西的帮助下知道问题的原因。但是如果你超出缓冲区而你没有收到任何错误,你会在寻找问题的原因时发疯。因为找到这种错误真的很难。你甚至可以不了解它。因此,永远不要超过在HEAP中分配的缓冲器。实际上,微软的C Runtime有一个特殊的&#34;调试&#34;可以在运行时找到这些错误的malloc版本,但是你需要使用&#34; DEBUG&#34;编译应用程序。组态。还有一些特别的东西,比如Valgrind,但我对这些东西有一点经验。
好吧,我写了很多,抱歉我的英语,我还在学习它。希望它会对你有所帮助。
答案 1 :(得分:0)
首先,永远不要忘记释放你的记忆。
// NEVER FORGET TO FREE YOUR MEMORY
free(people);
至于这部分
//accessing memory on the heap I do not owe?
people[5].someone=Rob;
//prints "Robert" why is this okay? Am I potentially overwriting memory?
printf("%s\n",people[5].someone.name);
你只是幸运(或者我认为不幸,因为你没有看到你正在做的逻辑错误)。
这是未定义的行为,因为你有两个单元格,但是你访问第6个单元格,你就会超出界限。