C程序的构成及动态内存分配

对一个程序,通常的理解就是,源码编译成机器代码,然后通过机器解释运行。不过是怎样编译成机器代码,和怎样运行的,无疑是个值得探讨的问题。怎样编译成机器代码,过程就是源码的编译、链接,编译器做了这些事。而怎样运行,却不是哪个器件自己一己之力就可以做到的。机器代码要在机器上运行,就得要请求硬件资源。涉及最多的就是CPU和内存了。CPU进行逻辑控制和运算,内存用于运行过程中的数据的快速交互场所。

一个C程序从其自身代码的结构上来看,编译过后不过是一段代码。而这段代码,从磁盘系统加载到内存中被称为代码段或正文段(code/text segment)的地方。在内存中的正文段是共享的。所以,当我们运行同一程序的多个进程时,在内存中,只有一个程序代码的副本。当然,为了防止有人要做坏事,正文段的权限常常都是只读的。
一个只有谋略,而手下无兵的将领,在战场上是什么也做不了的。程序代码,当然也不可能纯粹的都是逻辑描述。还必须得有逻辑作用的对象和结果——数据。也即程序中的各种变量。不同变量,在程序中的地位看起来除了作用域与生存周期外,都大同小异。不过,它们的实现确实非常的不同的。

程序中的未初始化的全局变量,存储在一个被称为未初始化数据段的地方。也即是bbs(block started by symbol)段。同时,内核会自动的将此段中的数据初始化为0或空指针。如,函数外的声明:

int sum[10];

使得该变量存放在bbs段中。
既然有了未初始化的全局变量,当然也就有初始化了的全局变量。它们存放在一个称为初始化数据段(常简称为数据段)的地方。拥有全局的作用域整个程序的生存周期。(Right?) 如,任函数外的声明:

int tmp = 99;

使得该变量存放在数据段中。
当然,还有一个叫堆栈段的东西。自动变量及每次函数调用是所需要保存的信息都存放在这里。堆栈的特点是先进后出(FILO)及数据存放的周期短,即进栈出栈操作很频繁。自动变量通常都是作为临时变量存在的,存在周期短,非常时候放在栈中。(Right?) 而函数调用时的的现场信息,可以利用堆栈的先进先出特点很方便的进行保护和恢复。典型的,递归的实现就是用了堆栈进行,因为堆栈是一层层向下增长的,所以在子函数中是不会覆盖调用函数的参数的。特别得要注意的一点是,主函数main中的变量,也是自动变量。因为主函数也是函数啊!
最后,作为C和C++特有的动态内存分配。是在运行时,利用堆来动态的进行内存分配的。所以动态分配的内存都具有全局的作用域(从分配后开始)。而生存周期而是直到其被释放前。动态内存的分配,其实就是向内核请求一块内存资源,而释放呢,就是将资源返还给内核。所以动态分配的内存都必须在不再需要时释放。不然可能会造成内存泄露及动态分配失败等恼人的问题。
看到这里,我们可以发现。一个C程序,其代码是被加载到了内存的代码段中,而其代码段中的一个个的变量,并没有实际的存放数据,数据根据情况的不同存放在了bbs段,数据段,堆栈段及堆中,代码段中的变量,存放的是一个指向各个实际存放地方的指针。(Right?)
典型的C内存分布图:

再说说C中动态内存管理。在C中,主要由标准头文件<stdlib.h>中定义的几个函数来进行内存管理:

1 void* malloc(size_t size)
2
3 void* calloc(size_t nobj, size_t size)
4
5 void* realloc(void *p, size_t size)
6
7 void* free(void *p)

上面这四个函数,都返回一个void* 指针。在C中,void* 指针可以接收任意类型指针,同时,可以不经强制类型转换直接传递给任意类型指针。而在C++中,前一种情况一样,后一种情况必须进行强制类型转换。
先说说malloc,它接收一个size_t 类型的参数作为请求的内存的大小,以字节为单位。所以,在内存分配中,通常会用到sizeof运算符来获取要分配的数据类型的大小。如:

int *p = malloc(sizeof(int));

为一个int类型的指针 p 分配了一段内存区域。
calloc函数,用于为一个数组对象分配内存,第一个参数为数组的大小,第二个参数为每中数据类型的大小。一般calloc用得不多,可以直接用malloc替代。
realloc函数,用于调整已分配内存的大小。当你发现你的当前内存太多或太小时,就可以用realloc来进行内存的调整了。
所有这三个内存分配函数失败时都会返回NULL,所以可以通过检查返回值来判断内存分配是否成功。
最后,free函数,用于释放使用上述三个函数动态分配的内存。并且必须进行释放。
C中的内存分配函数的实现是由系统来完成的。所以不同系统会有不同的实现。UNIX-like系统中,一般是由sbrk系统调用来实现的。(更具体?)
在C++中,内存管理主要用操作符new 和 delete。相对C来说更方便一些,并且效率更高。不过,有个地方我没想明白的是,为何STL中的内存管理要单独写一个allocator来实现。还是new和delete不够强大?嗯,毕竟不了解new 和 delete的实现。

转载请注明地址: http://www.qyspaces.com/?p=262

时间: 2024-10-06 17:34:09

C程序的构成及动态内存分配的相关文章

【好程序员笔记分享】——动态内存分配

<A href="http://www.goodprogrammer.org/" target="blank">ios培训</A>------我的c语言笔记,期待与您交流! 再C语言中关于内存是一个很重要的知识点,所以今天我就从c语言的内存分配开始为大家解析一下C语言再iOS开发中非常重要的一些知识. 1:malloc函数的介绍 C语言中开辟内存空间:malloc函数 再C语言中malloc原理大致是这样的: malloc函数的实质体现在,它

C++动态内存分配

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解>电子工业出版社等. CSDN视频网址:http://edu.csdn.net/lecturer/144 C / C ++中的动态内存分配是指程序员手动执行内存分配, 动态分配的内存分配给堆,非静态和局部变量获取在Stack上分配的内存.详情查看上篇博文:C程序的内存布局. 什么是应用程序? 动态分配的

【C语言天天练(九)】动态内存分配

引言:数组的元素存储于内存中连续的位置上.当一个数组被声明时.它所须要的内存在编译时就被分配. 可是,我们能够使用动态内存分配在执行时为它分配内存. 一块内存的生命周期能够分为四个阶段:分配.初始化.使用.释放. 内存的分配一般使用C函数库里的malloc函数(原型:void *malloc(size_t size)). 关于malloc函数应该注意一下几点: 1.malloc的參数就是须要分配的内存的字节数. 2.malloc所分配的是一块连续的内存. 3.分配成功.则返回指向分配内存起始地址

SQLite剖析之动态内存分配

SQLite通过动态内存分配来获取各种对象(例如数据库连接和SQL预处理语句)所需内存.建立数据库文件的内存Cache.以及保存查询结果.我们做了很多努力来让SQLite的动态内存分配子系统可靠.可预测.健壮并且高效.本文概述SQLite的动态内存分配,软件开发人员在使用SQLite时可以据此获得最佳性能. 1.特性    SQLite内核和它的内存分配子系统提供以下特性:    (1)对内存分配失败的健壮处理.如果一个内存分配请求失败(即malloc()或realloc()返回NULL),SQ

继承和动态内存分配

假设基类使用了动态内存分配,而且定义了析构函数.复制构造函数和赋值函数,但是在派生类中没有使用动态内存分配,那么在派生类中不需要显示定义析构函数.复制构造函数和赋值函数. 当基类和派生类采用动态内存分配时,派生类的析构函数.复制构造函数.赋值运算符都必须使用相应的基类方法来处理基类元素.这种要求是通过三种不同的方式来满足的.对于析构函数.这是自动完成的,也就是说在派生类的析构函数中无需显示调用基类的析构函数.对于构造函数,这是通过在初始化成员类别中调用基类的复制构造函数来完成的,如果不这样做,将

【Cpp】考点·堆栈&amp;动态内存分配

动态内存分配 堆内存分配与释放 C/C++定义了四个内存区间:代码区,全局变量与静态变量区,局部变量区(栈区),动态内存区(堆区) 通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间.这种内存分配称为静态存储分配.有些操作对象只在程序运行时才确定,这样编译时无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配.所有动态存储分配都在堆区中进行. 当程序运行到需要一

C++ 动态内存分配(6种情况,好几个例子)

1.堆内存分配 : C/C++定义了4个内存区间: 代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或自由存储区(free store). 堆的概念: 通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间.这种内存分配称为静态存储分配: 有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分

28._动态内存分配

动态内存分配 传统数组的缺点(静态内存分配):   1.数组长度必须事先指定,且只能是常整数,不能是变量    例子:     int a[5]; //OK     int len = 5; int a[len]; //error   2.传统形式定义的数组,该数组的内存程序员无法手动编    程释放,在一个函数运行期间,系统为该函数中数组所     分配的空间会一直存在,直到该函数运行完毕时,数组    元素所占存储空间才会被系统释放 3.数组的长度一旦定义,长度就能被改变     4.A函数

Lesson(DynamicMerry)动态内存分配

//  main.m //  1-27随堂笔记 //讲师: 小辉 //笔者: 王学文 //  Created by lanouhn on 15/1/27. //  Copyright (c) 2015年 lanouhn. All rights reserved. //动态内存分配(DynamicMerry) #import <Foundation/Foundation.h> void test() { int x = 10; int y = 20; } //函数返回栈区的数据,是不安全的;一i