http://blog.sina.com.cn/hotplum[订阅]
个人资料
评论
读取中...
图片幻灯
友情链接
访客
读取中...
好友
读取中...
分类
    内容读取中…
博文

?/P>

下面说说动态内存分配的问题!

从前面讨论可以知道,在c语言中由#define定义的常量是在预编译做了宏替换,而在程序中的这些字面量有的被编译到代码中(ELF文件的.text段),有的在只读数据段(比如字符串,另外双精度double占用8个字节,运算时需要FPU 的ST寄存器或者叫浅栈,不容易编译到代码中),剩下的有初值的外部变量(包括static限定的变量)在数据段(.data),为赋初值的外部变量在.bss段中(ELF文件没有给分配空间,加载到内存映像时分配),还有一种是局部变量(自动变量)在用户栈中(参看前面的内存映像图).前面我们讲过变量生存期的问题,局部变量决定于调用栈,生存周期在函数调用过程中,外部变量(包括static限定的变量)在整个进程运行期中,还有一种情况就是通过malloc函数调用动态生存期的变量,他们的生存期是程序员自己控制的(其实,还要取决于malloc等函数的实现,不过先这么认为吧)!理解动态内存分配先要有下面的前提概念:

  首先,内存分配(包括内核加载,用户进程加载,动态库加载等等)都是建立在操作系统的虚拟内存分配之上的!关于虚拟内存分配

(四)动态内存分配

内存的使用问题和并发问题似乎占据了程序员一半以上的困扰!所以在现代一些语言中,尽量避开程序员自己直接控制内存的使用(主要是动态内存分配的使用).在若干年前的dos时代储存器基本上是公开访问的,现在操作系统给程序员加入各种屏障! 但在嵌入式领域,为了追求性能,操作系统去掉了虚拟存储器的概念,变量指向了实际的内存地址!

虚拟存储器是一个抽象的概念,当你的程序引用一个”内存”变量,你不知道他的实际存贮在什么地方,是物理内存,是磁盘虚拟内存,还是高速缓存中等等!

 下面是一个存储器的层次金字塔图形

存储器层次金字塔.JPG

 

 

(二)程序级异常

对于一种现代编程语言来说,对异常控制流的支持是非常重要的!尤其是开发过类库的同学一定深有体会! 举个例子,比如你开发一个读取某种自定义XML格式的类库(比如叫做LFML吧,用来描述一种特定业务),当用户程序使用你的类库时有一种情况是肯定的,就是不管你的LFML描述的格式多么复杂(肯能需要许多函数去解析,还有函数的嵌套调用),但如果格式被破坏,只要有一条不符合你的LFML规则,你的解析就不需要进行下去了!这样的程序如何实现?如果有异常控制流,只需要在任意函数(不管嵌套多少层)中抛出一个异常,在客户程序中捕获这个异常就可以了!

当然程序级的ECF(异常控制流)还有许多有用的地方!

在操作系统中,举个例子,你的程序正在执行,这是用户按下一个ESC键,操作系统如何告知你ESC键按下(或者正在运行的程序如何知道ESC键按下),在dos时代有个常见的词-中断.

其实中断也是一种ECF!还有你的程序在调用内核的功能时(因为你的程序运行在用户空间),内核运行在内核空间,如何完成这种调用呢?还有,操作系统是如何完成多个进程(线程)”同时”执行的呢?这些都是OS级的ECF!需要这方面的资料可以baidu,google!

  我这里只讨

(3)    静态链接和动态链接.

关于可执行文件格式,有许多好于的工具可以使用:window平台上查看PE格式的工具很多(比如PEJump等,百度google去吧),Linux上有个很好的工具是objdump,自己看帮助文件objdump还可以反汇编哦.

 静态链接比较简单,其实就是将obj文件打包一下.先做个试验看看吧!

就使用上面用到的main1.c strtest.h strlen.c externvalue.c ,我们再家一个文件strcpy.c

#include 'strtest.h'

void strcpy_lf(char *dest,const char *src)

{

  while((*dest++ = *src++) != STREOF)

         ;

}

(我使用了stcpy_lf 这样的名字是区别于标准c库的,因为在gcc编译是默认链接标准库的,

会有名字冲突,你可以用-static 参数编译main2.c看看链接后的可执行文件大小)

main2.c

#include <stdio.h>

#include 'strtest.h'

#define HELLO 'Hello,world'

int main(int argc, char *argv[])

{

 

        char str

 

(2)    可执行文件的格式和内存映像

在windows平台上可执行程序就是exe文件,dos时代的小于64K的com文件现在已经不见了!windows上可执行文件格式我们一般成为PE(Portable Executable)文件(.net 编译成的exe也是PE文件,但有本质的差别),PE文件源于Unix系统的COFF文件.现代Unix系统,比如Linux使用一种叫ELF(Executable and Linkable Format)的文件,意思是可执行和可链接的文件,名称概况的很好! 可执行文件,可重定位的目标文件(obj)以及动态链接库文件(.so)文件都是ELF文件. Windows上的动态库是DLL文件!

  Windows的PE格式也比较简单,大概由4个部分组成文件头,节表,节以及其他如调试信息等部分.文件头至今还保留了Dos文件头(dos时代比较熟悉的4D5A(“MZ”)开头),以及输出一句提示语”This program must be run under Wind32”.

其实PE格式和ELF格式都有许多相同的地方,包括可执行文件加载到内存中的内存映像(就是进程,进程、文件和虚拟储存器是操作系统的几个重要的抽象概念,当然windows是以线程调度的,这在后面的内容讨论)也是大同小异!所以就简单的说说linux中的ELF。详细深入的研究可以参考各种关于ELF文件的文

(一)可执行文件可链接文件.

(涉及到编译原理,操作系统,动态链接等知识)

(1)    c程序的编译过程.

一个c程序的文本文件(ASCII码文件)如何变成一个可执行的程序? C程序大概需要这么几步:

 1预处理

 2编译

 3汇编

 4链接

 

预处理是将c文件中的根据#开始的命令修改生成新的源程序,比如宏替换,#include将相应的文件内容包含进来.

编译就是把预处理后的c源文件转换成汇编语言源文件(仍然是文本文件).对于特定的宿主(操作系统和CPU体系),汇编语言是一样的,c和pascal编译后得到一样的汇编语言.所以这样类型的语言都可以叫做编译语言

汇编是把汇编语言文件翻译为机器指令(二进制文件).这种格式一般叫可重定位目标程序

就是大家熟悉的obj文件,(下面的文件格式再说)

链接链接的结果是一个可执行文件(一般指由操作系统直接加载的文件).程序中用到的外部变量和函数的定义是在链接过程中实际定位的!编译汇编时只需要有声明就可以了!(相关的知识需要了解链接时的符号解析等等,有兴趣的可以看看).

 

例子说明!

 

 

C语言的特性相对比较少,其他类c语言也是一样.OO的语言中的class可以看做一个特殊的struct,对象的方法其实就是函数(只是要求有一个默认的指向其对象的地址作为参数,比如this).用c现有的元素也可以模拟许多OO的特性,比如interface,多态等等.我见过一些纯c程序,也使用了后绑定,interface等特性降低程序的耦合以及其他优良的特性.

 

知道c语言的语言特性,就可以编程了吗?当然可以,但仅仅是语言本身除了学习之外,好像还没有别的什么用途!

 

那么,真正的软件开发需要哪些知识呢?

先扯点题外话

 ( 看上去好像与软件开发无关,其实却是软件开发成功决定性的一个概念: “软件工程”

人类似乎总是遭到天神的嘲弄,比如给了人类了解”天意”的智慧,却没有给予足够的时间,人类连自己也搞不清楚,却要去”改造大自然”.软件行业也是一个典型,现代自动化和高科技支撑产业的软件行业,却是几千年前就存在的”手工业作坊”,知识工具变成了键盘,鼠标.

  一个软件的成功有许多环境,包括可行性研究,需求分析,设计,开发,测试,发布,维护等等,软件的开发只是其中一部分,随着需求的复杂度的提升,”开发”

函数和程序结构

1.函数

函数调用非常依赖编译实现,函数的参数和返回值一般存放在线程堆栈中.

对于c语言,函数都是外部的!(函数中不能包含函数)

重要的一条,函数的参数传递统一为值传递

这一点很重要!

一般来说,在调用一个函数时,参数是放在栈里的(有时候编译器会把参数放到寄存器中,不影响我们讨论),值传递的意思是,编译器会将传递给函数的参数(我这里不打算引入形参和实参的概念,其实或许无助于理解)的值赋值到栈中,这样的处理会使编译简单,也使程序员容易理解.

int AddTen(int aNum)

{

  return aNum + 10;

}

int a =100;

int main()

{

  int b =AddTen(a);

return 0;

}

调用函数AddTen 时 变量a的值 100 被赋值到栈中,也就是在函数AddTen中 aNum是一个局部变量,值是100.

传值的缺点是明显的,如果参数尺寸很大,则复制会影响性能,而且大多数复制是没有必要的!

另外,如果一个函数有多个返回值怎么办?

有经验的C程序员在传递尺寸大的参数或者需要多个返回值时,会采用传递指针的方法:

比如上面定

(3)    生存期

c语言数据对象的生存期 分为三种:

外部变量: 生存期由编译器和操作系统决定,一般来说生存期从定义开始到程序结束为止.

内部变量(c语言也叫auto,另外寄存器变量就不考虑了),一般在函数内部(函数的子代码块中的定义的变量也一样)从定义开始(其实是函数开始),到函数结束为止.

动态创建的数据对象只是那些比如通过malloc调用得到的内存块上的数据对象,生存期靠程序员决定(不错,给程序员很大的发挥空间,不过是把”双刃剑”,以至于java不允许干这个事情).

 

另外注意一个关键字 static

如果是外部变量static修饰限制了这个外部变量的作用域,只能在本文件中使用.

如果是内部变量static ,改变了这个变量的生存期(类似一个外部变量).

当然函数也是外部的,如果用 static修饰,也是改变了这个函数的作用域,只能在本文件中使用.

没有static修饰的外部变量其实作用域可以是整个程序.

另外c引入c++的const关键字只是说明该变量的只读特性.仅此而已.

 

 

 

复杂类型的定义与声明

 

数组  &