深入内存

今天在网上看到了这样一个问题,“如果malloc 了一块字符串的内存,然后,它改变了这个字符串的大小,问会不会有一部分内存没有被释放掉。”这个问题,以前的确没有仔细想过。

  当然,我觉得是肯定会释放掉的,但是一直没有了解过free 的原理,不敢乱说。我看了一下操作系统的内存管理,基本上是这样的,当然各个系统的实现不一样。

  操作系统管理内存,维护了一个空闲内存链表,malloc从个链表中选出一个来使用,每个内存块都有一个头部来表示这个内存的基本信息,如内存大小,所以free 时候 能够记住原来指针所指的内存大小,而不是用内存块中是否有 \0 来临时计算指向内存的大小,不要字符串的计算长度的方法所误导。

  还有一点要注意的就是,系统在free 内存的时候,记住的只是 malloc 时候的地址,和 分配内存的大小。

  比如 char *p = (char *)malloc(10); 就会产生分配10个 字节。如果 你把指针的地址改变了 p = p + 1; 然后 free 就要出问题了。程序会崩溃。

  如果一定要改变指针的值,建议这样做 char *newp = p; 然后 改变 newp = newp + 1 , 最后 free(p);

  还有一点要注意,一个长度 为10 的字符串 要占用 11个字节。因为还有一个 ‘\0‘, 所以分配内存的时候要分配 lenght + 1 的大小。

//********************************************************************************************************

使用C语言编程,实际上使用的内存只有一种——虚拟内存。根据功能的不同在C语言中又将虚拟内存为分三类:栈区、堆区、静态数据区,无论单一变量还是数组,其内存分配都是如此。其中,栈区、静态数据区、堆区都会有编译器负责分配、操作系统负责管理,程序员可以在堆区使用malloc()来动态分配堆内存。

1、栈区:一般每一个函数对应一个栈区,在编译原理中称为栈帧。比如下面的代码:

int main()
{
//定义一个有20个int元素的数组。此时数组a分配的虚拟内存称为栈区,有编译器自行分配。
    int a[20] = {0}; 
    return 0;
}

2、静态数据区:这实际上对应于生成的可执行文件的.data区段,因为这个区段在生成的可执行文件中,因此是“静态的”。比如下面的代码:

//定义一个20个int元素的全局数组,此时数组分配的虚拟内存称为静态数据区,有编译器自行分配。
int g_a[20]; 
int main() { return 0;}

3、堆区:堆区是最复杂的,有操作系统负责堆管理,但是当用C语言编译器生成一个可执行文件并且运行时,它会默认建立一些堆。拿Windows来说,每一程序运行,它会建立至少两个堆,一个是默认堆,一个是new堆。比如下面的代码:

int main()
{
    int *pa = (int*)malloc(sizeof(int)*20);//分配20个int元素大小的堆空间。
    return 0;
}


Even though with the .NET framework we don‘t have to actively worry about memory management and garbage collection (GC), we still have to keep memory management and GC in mind in order to optimize the performance of our applications. Also, having a basic understanding of how memory management works will help explain the behavior of the variables we work with in every program we write.  In this article I‘ll cover the basics of the Stack and Heap, types of variables and why some variables work as they do.

There are two places the .NET framework stores items in memory as your code executes.  If you are not yet familiar with them, let me introduce you to the Stack and the Heap. Both the Stack and Heap help us run our code. They reside in the operating memory on our machine and contain the pieces of information we need to make it all happen.

Stack vs. Heap: What‘s the difference?

The Stack is more or less responsible for keeping track of what‘s executing in our code (or what‘s been "called").  The Heap is more or less responsible for keeping track of our objects (our data, well... most of it; we‘ll get to that later).

Think of the Stack as a series of boxes stacked one on top of the next.  We keep track of what‘s going on in our application by stacking another box on top every time we call a method (called a Frame).  We can only use what‘s in the top box on the Stack.  When we‘re done with the top box (the method is done executing) we throw it away and proceed to use the stuff in the previous box on the top of the Stack. The Heap is similar except that its purpose is to hold information (not keep track of execution most of the time) so anything in our Heap can be accessed at  any time.  With the Heap, there are no constraints as to what can be accessed like in the Stack.  The Heap is like the heap of clean laundry on our bed that we have not taken the time to put away yet; we can grab what we need quickly. The Stack is like the Stack of shoe boxes in the closet where we have to take off the top one to get to the one underneath it.



The picture above, while not really a true representation of what‘s happening in memory, helps us distinguish a Stack from a Heap. The Stack is self-maintaining, meaning that it basically takes care of its own memory management.  When the top box is no longer used, it‘s thrown out.  The Heap, on the other hand, must worry about Garbage collection (GC), which deals with how to keep the Heap clean (no one wants dirty laundry laying around, it stinks!).

What goes on the Stack and Heap?

We have four main types of things we‘ll be putting in the Stack and Heap as our code is executing: Value Types, Reference Types, Pointers, and Instructions.

Value Types:

In C#, all the "things" declared with the following list of type declarations are Value types (because they are from System.ValueType):
  • bool
  • byte
  • char
  • decimal
  • double
  • enum
  • float
  • int
  • long
  • sbyte
  • short
  • struct
  • uint
  • ulong
  • ushort
Reference Types: All the "things" declared with the types in this list are Reference types (and inherit from System.Object, except, of course, for object which is the System.Object object):
  • class
  • interface
  • delegate
  • object
  • string
Pointers: The third type of "thing" to be put in our memory management scheme is a Reference to a Type. A Reference is often referred to as a Pointer. We don‘t explicitly use Pointers, they are managed by the Common Language Runtime (CLR). A Pointer (or Reference) is different than a Reference Type in that when we say something is a Reference Type, it means we access it through a Pointer. A Pointer is a chunk of space in memory that points to another space in memory.  A Pointer takes up space just like any other thing that we‘re putting in the Stack and Heap and its value is either a memory address or null. Instructions: You‘ll see how the  "Instructions" work later in this article... How is it decided what goes where? (Huh?) Ok, one last thing and we‘ll get to the fun stuff. Here are our two golden rules:
  1. A Reference Type always goes on the Heap; easy enough, right?
  2. Value Types and Pointers always go where they were declared. This is a little more complex and needs a bit more understanding of how the Stack works to figure out where "things" are declared.
The Stack, as we mentioned earlier, is responsible for keeping track of where each thread is during the execution of our code (or what‘s been called).  You can think of it as a thread "state" and each thread has its own Stack.  When our code makes a call to execute a method the thread starts executing the instructions that have been JIT compiled and live on the method table, it also puts  the method‘s parameters on the thread Stack. Then, as we go through the code and run into variables within the method, they are placed on top of the Stack. This will be easiest to understand with an example. Take the following method: public int AddFive(int pValue)          {                int result;                result = pValue + 5;                return result;          } Here‘s what happens at the very top of the Stack.  Keep in mind that what we are looking at is placed on top of many other items already living in the Stack: Once we start executing the method, the method‘s parameters are placed on the Stack (we‘ll talk more about passing parameters later). NOTE : the method does not live on the stack and is illustrated just for reference. Next, control (the thread executing the method) is passed to the instructions to the AddFive() method which lives in our type‘s method table, a JIT compilation is performed if this is the first time we are hitting the method. As the method executes, we need some memory for the "result" variable and it is allocated on the Stack. The method finishes execution and our result is returned. And all memory allocated on the Stack is cleaned up by moving a pointer to the available memory address where AddFive() started and we go down to the previous method on the stack (not seen here). In this example, our "result" variable is placed on the stack.  As a matter of fact, every time a Value Type is declared within the body of a method, it will be placed on the stack. Now, Value Types are also sometimes placed on the Heap.  Remember the rule, Value Types always go where they were declared?  Well, if a Value Type is declared outside of a method, but inside a Reference Type then it will be placed within the Reference Type on the Heap. Here‘s another example. If we have the following MyInt class (which is a Reference Type because it is a class): public class MyInt          {                       public int MyValue;          } and the following method is executing: public MyInt AddFive(int pValue)          {                MyInt result = new MyInt();                result.MyValue = pValue + 5;                return result;          } Then just as before, the thread starts executing the method and its parameters are placed on sthe thread‘s stack. Now is when it gets interesting. Because MyInt is a Reference Type, it is placed on the Heap and referenced by a Pointer on the Stack. After AddFive() is finished executing (like in the first example), and we are cleaning up... we‘re left with an orphaned MyInt in the Heap (there is no longer anyone in the Stack standing around pointing to MyInt)! This is where the Garbage Collection (GC) comes into play.  Once our program reaches a certain memory threshold and we need more Heap space, our GC will kick off.  The GC will stop all running threads (a FULL STOP), find all objects in the Heap that are not being accessed by the main program and delete them.  The GC will then reorganize all the objects left in the Heap to make space and adjust all the Pointers to these objects in both the Stack and the Heap.  As you can imagine, this can be quite expensive in terms of performance, so now you can see why it can be important to pay attention to what‘s in the Stack and Heap when trying to write high-performance code. Ok, that‘s great, but how does it really affect me? Good question. When we are using Reference Types, we‘re dealing with Pointers to the type, not the thing itself.  When we‘re using Value Types, we‘re using the thing itself.  Clear as mud, right? Again, this is best described by example. If we execute the following method: public int ReturnValue()          {                int x = new int();                x = 3;                int y = new int();                y = x;                      y = 4;                          return x;          } We‘ll get the value 3.  Simple enough, right? However, if we are using the MyInt class from before: public class MyInt          {                public int MyValue;          } and we are executing the following method: public int ReturnValue2()          {                MyInt x = new MyInt();                x.MyValue = 3;                MyInt y = new MyInt();                y = x;                                 y.MyValue = 4;                              return x.MyValue;          } What do we get?    4! Why?...  How does x.MyValue get to be 4? Take a look at what we‘re doing and see if it makes sense: In the first example everything goes as planned: public int ReturnValue()          {                int x = 3;                int y = x;                    y = 4;                return x;          } In the next example, we don‘t get "3" because both variables "x" and "y" point to the same object in the Heap. public int ReturnValue2()          {                MyInt x;                x.MyValue = 3;                MyInt y;                y = x;                                y.MyValue = 4;                return x.MyValue;          } Hopefully this gives you a better understanding of a basic difference between Value Type and Reference Type variables in C# and a basic understanding of what a Pointer is and when it is used.  In the next part of this series, we‘ll get further into memory management and specifically talk about method parameters. For now... Happy coding.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2025-01-31 16:50:05

深入内存的相关文章

JavaScript的进阶之路(二)函数简介,变量、作用域和内存问题

<h3>ECMAScript中函数不存在函数签名的概念,没有重载</h3><h3>无需指定返回值,可以在任何时候返回任何值.未指定返回值的函数,返回的是一个特殊的undefined值</h3> <script type="text/javascript"> function sayHi(){ console.log("Hi"); }; sayHi(); function sayName(name,age){

【c/c++】内存分配大小

测试平台:linux 32位系统 用sizeof()运算符计算分配空间大小.单位:字节 1. 数组名与变量名的区别 int main() { char q[] = "hello"; cout << "q:" << sizeof(q) << endl; char *mq = q; cout << "mq:" << sizeof(mq) << endl; const char *

Performance Monitor3:监控SQL Server的内存压力

SQL Server 使用的资源受到操作系统的调度,同时,SQL Server在内部实现了一套调度算法,用于管理从操作系统获取的资源,主要是对内存和CPU资源的调度.一个好的数据库系统,必定在内存中缓存足够多的信息,以减少从物理硬盘中读取数据的次数:如果内存是系统瓶颈,那么SQL Server一定会运行的非常慢.监控SQL Server的内存压力,需要从Widnows级别上,对内存使用的整体使用情况进行监控:从SQL Server级别上,监控SQL Server对内存资源的使用情况. 一,从Wi

试试SQLSERVER2014的内存优化表

原文:试试SQLSERVER2014的内存优化表 试试SQLSERVER2014的内存优化表 SQL Server 2014中的内存引擎(代号为Hekaton)将OLTP提升到了新的高度. 现在,存储引擎已整合进当前的数据库管理系统,而使用先进内存技术来支持大规模OLTP工作负载. 就算如此,要利用此新功能,数据库必须包含"内存优化"文件组和表 即所配置的文件组和表使用Hekaton技术. 幸运的是,SQL Server 2014使这一过程变得非常简单直接. 要说明其工作原理,我们来创

Linux内存VSS,RSS,PSS,USS解析

转载:http://myeyeofjava.iteye.com/blog/1837860 adb shell procrank | grep com.package > appmem说明:五个参数分别为PID Vss Rss Pss Uss 一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)RSS - Resident Set Size 实际使用物理内存(包含共享库占用

函数一直无法立即退出,在等待了大约30s后才能退出(QMulitHash释放不连续的内存需要很长世间,而这样设置局部变量后又无法避免这个问题)

局部变量使用对性能的影响以及进程的堆和栈: 由于在代码中我使用了QMulitHash<QString , LHFilteVersionItem> tmp;这一局部变量来保存某一目录下的文件,由于在写测试代码期间,我利用循环模拟了50万的数据序列化后保存在文件中,在运行期间我发现读取函数耗费很长的时间,而函数里面最耗时的读取操作也只花费了很短的时间,但是函数一直无法立即退出,在等待了大约30s后才能退出,相关代码如下: [cpp] view plain copy void LHTWORKFLOW

内存池、进程池、线程池

首先介绍一个概念"池化技术 ".池化技术 一言以蔽之就是:提前保存大量的资源,以备不时之需以及重复使用. 池化技术应用广泛,如内存池,线程池,连接池等等.内存池相关的内容,建议看看Apache.Nginx等开源web服务器的内存池实现. 起因:由于在实际应用当中,分配内存.创建进程.线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作.           因此,当程序中需要频繁的进行内存申请释放,进程.线程创建销毁等操作时,通常会使用内存池.进程池.

JavaScript栈和堆内存,作用域

1.栈 stack"和"堆 heap": 简单的来讲,stack上分配的内存系统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里.stack一般是静态分配内存,heap上一般是动态分配内存. 2.基本类型和引用类型: 基本类型:存放在栈内存中的简单数据段.数据大小确定,内存空间大小可以分配. 引用类型:存放在堆内存中的对象,变量中实际保存的是一个指针,这个指针指向另一个位置.每个空间大小不一样,要根据情况开进行特定的分配. 详见<Javas

jvm java内存区域的介绍

jvm虚拟机在运行时需要用到的内存区域.广泛一点就是堆和栈,其实不然,堆和栈只是相对比较笼统的说法,真正区分有如下几个 先上图一: 总的就是 java的内存模型 内存模型又分堆内存(heap)和方法区(有时也称为non-heap)和栈 堆又分新生代(Young)和老年代(old/Tenured) 新生代又分默认比例为8:1:1的eden空间.from survivor空间.to survivor空间 当进行垃圾回收时,eden.survivor from 存活得对象会复制到servivor to

分布式缓存技术redis学习系列(二)——详细讲解redis数据结构(内存模型)以及常用命令

Redis数据类型 与Memcached仅支持简单的key-value结构的数据记录不同,Redis支持的数据类型要丰富得多,常用的数据类型主要有五种:String.List.Hash.Set和Sorted Set. Redis数据类型内存结构分析 Redis内部使用一个redisObject对象来表示所有的key和value.redisObject主要的信息包括数据类型(type).编码方式(encoding).数据指针(ptr).虚拟内存(vm)等.type代表一个value对象具体是何种数