- 浏览: 140753 次
文章分类
最新评论
C 程序一般是由下列几部分组成:
(1)正文段。这是由 CPU 执行的机器指令部分,它通常是可共享的,所以即使是频繁执行的程序(如文本编辑器、C 编译器和 shell 等)在存储器中也只有一个副本。另外,正文段通常是只读的,以防止程序由于意外而修改其指令。
(2)初始化数据段(简称数据段)。它包含了程序中被明确地赋初值的全局变量等。
(3)未初始化数据段(也称 bss 段,即“由符号开始的块”(block started by symbol))。函数外的未被初始化声明的变量就被存放在这里。程序开始前,内核将此段中的数据初始化为 0 或空指针。
(4)栈。自动变量及函数每次调用时所需保存的信息(如返回地址、调用者的环境信息等)都存放在栈中。注意,栈的增长方向一般是从高地址往低地址方向增长的。
(5)堆。主要用于动态存储分配,一般位于未初始化数据段和栈之间。
下图是这些段的一种典型存储空间布局。
从图中可知,未初始化数据段的内容并不存放在磁盘程序文件中,因为内核在程序开始运行前将它们都设置为 0。需要存放在磁盘程序文件中的只有正文段和初始化数据段。
实际上,编译好的可执行程序中还有若干其他类型的段,如包含符号表的段、包含调试信息的段以及包含动态共享库链接表的段等,不过它们并不装载到进程执行的程序映像中。
size 命令可报告正文段、数据段和 bss 段的长度(以字节为单位)。如:
其中第 4 列和第 5 列分别是以十进制和十六进制表示的前 3 段的总长度。
ISO 说明了 3 个用于存储空间动态分配的函数。
这 3 个函数返回的指针都是适当对齐的,可用于任何数据对象。其中 malloc 分配指定字节数的存储区,其中的初始值不确定;calloc 为指定数量指定长度的对象分配存储空间,其中的每一位都初始化为 0;realloc 可增加或减少以前分配区的长度。当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定。realloc 的 newsize 参数是存储区的新长度,当 ptr 是空指针时,realloc 的功能就如同 malloc。free 函数可用来释放 ptr 指向的存储空间,以便以后再分配。
这些分配例程通常用 sbrk 系统调用实现,该系统调用可以扩充或缩小进程的堆。但是大多数 malloc 和 free 的实现都不减小进程的存储空间。释放的空间可供以后再分配,但将它们保持在 malloc 池中而不返回给内核。一般所分配的存储空间都比所要求的要稍大一些,额外的空间会用来记录管理信息,比如分配块的长度、指向下一个分配块的指针等。因此在动态分配的缓冲区前或后进行写操作极有可能会改写另一块的管理记录信息。另外,释放一个已经释放了的块或者释放不是由 alloc 类函数分配的块等都有可能导致致命性的错误。而如果不释放不再使用的空间,则会使进程地址空间长度慢慢增加,直至不再有空闲空间,这被称为泄漏(leakage)。这会导致过度的换页开销,从而造成性能下降。
每个程序都接收到一张环境表,它是一个字符指针数组,其中每个指针都包含一个指向以 null 结束的 C 字符串的地址,全局变量“extern char **environ”则包含了该指针数组的地址,通常称 environ 为环境指针,指针数组为环境表,其中各指针指向的字符串为环境字符串。例如,一个以 5 个字符串组成的环境看起来大致如下图所示。
main 函数在大多数 UNIX 系统上的原型是这样的:
int main(int argc, char **argv[], char *envp[]);
其中第三个就是环境表地址。不过因为该参数比起 environ 也没有带来多少益处,因此 POSIX.1 也规定应使用 environ 而不使用第 3 个参数。通常用 getenv 和 putenv 等函数来访问和设置特定的环境变量,而不是直接使用 environ,但如果要查看整个环境,则必须使用 environ 指针。
其中:
* getenv 是用来获取某个环境变量的值。
* putenv 取形式为“name=value”的字符串,将其放到环境表中。如果 name 已经存在,则先删除其原来的定义。
* setenv 将 name 设置为 value。如果 name 已经存在,则根据参数 rewrite 的值为 0与否来决定是否覆盖原来的定义。
* unsetenv 删除 name 的定义,即使不存在也不算出错。
注意,putenv 和 setenv 的差别在于:putenv 可以自由地将传递给它的参数字符串直接放到环境中(但不应该将存放在栈中的字符串传给它);setenv 必须分配存储空间,以便依据其参数创建 name=value 字符串。
了解这些函数是如何修改环境表也是非常有益的。其中,环境表和环境字符串通常存放在进程存储空间的顶部(栈之上)。删除一个字符串时只需要在环境表中找到该指针,然后将所有后续指针都向环境表首部顺次移动一个位置。但是增加一个字符串或修改一个现有的字符串就困难得多。因为环境表和环境字符串通常占用的是进程地址空间的顶部,所以它不能再向高地址方向扩展,同时也不能移动在它之下的各栈帧。两者结合使得该空间的长度不能再增加。
(1)当修改一个现有的 name 时:
a. 如果新 value 的长度不大于现有的长度,则只要复制新字符串到原字符串所在的空间即可。
b. 如果新 value 的长度大于原长度,则必须调用 malloc 为新字符串分配空间,然后将新字符串复制到该空间,接着使环境表中的针对 name 的指针指向新分配区。
(2)当增加一个新的 name 时就复杂多了。必须先调用 malloc 为 name=value 字符串分配空间,然后将该字符串复制到此空间中。
a. 如果这是第一次增加一个新的 name,则必须调用 malloc 为新的指针表分配空间。接着,将原来的环境表复制到新分配区,并将指向新 name=value 字符串的指针存放在该指针表的表尾,然后又将一个空指针存放在其后。最后使 environ 指向新指针表。如果原来的环境表位于栈顶之上,那么必须将此表移至堆中。但是此表中的大多数指针仍指向栈顶之上的各 name=value 字符串。
b. 如果这不是第一次增加一个新的 name,则说明之前已在堆中为环境表分配了空间,所以只要调用 relloc,以分配比原空间多存放一个指针的空间,然后将指向新 name=value 的字符串的指针存放在该表表尾,后面跟着一个空指针。
(1)正文段。这是由 CPU 执行的机器指令部分,它通常是可共享的,所以即使是频繁执行的程序(如文本编辑器、C 编译器和 shell 等)在存储器中也只有一个副本。另外,正文段通常是只读的,以防止程序由于意外而修改其指令。
(2)初始化数据段(简称数据段)。它包含了程序中被明确地赋初值的全局变量等。
(3)未初始化数据段(也称 bss 段,即“由符号开始的块”(block started by symbol))。函数外的未被初始化声明的变量就被存放在这里。程序开始前,内核将此段中的数据初始化为 0 或空指针。
(4)栈。自动变量及函数每次调用时所需保存的信息(如返回地址、调用者的环境信息等)都存放在栈中。注意,栈的增长方向一般是从高地址往低地址方向增长的。
(5)堆。主要用于动态存储分配,一般位于未初始化数据段和栈之间。
下图是这些段的一种典型存储空间布局。
从图中可知,未初始化数据段的内容并不存放在磁盘程序文件中,因为内核在程序开始运行前将它们都设置为 0。需要存放在磁盘程序文件中的只有正文段和初始化数据段。
实际上,编译好的可执行程序中还有若干其他类型的段,如包含符号表的段、包含调试信息的段以及包含动态共享库链接表的段等,不过它们并不装载到进程执行的程序映像中。
size 命令可报告正文段、数据段和 bss 段的长度(以字节为单位)。如:
$ size /usr/bin/cc /bin/sh text data bss dec hex filename 346919 3576 6680 357175 57337 /usr/bin/cc 102134 1776 11272 115182 1c1ee /bin/sh
其中第 4 列和第 5 列分别是以十进制和十六进制表示的前 3 段的总长度。
ISO 说明了 3 个用于存储空间动态分配的函数。
#include <stdlib.h> void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void *ptr, size_t newsize); /* 返回值:若成功,都返回非空指针;否则,都返回 NULL */ void free(void *ptr);
这 3 个函数返回的指针都是适当对齐的,可用于任何数据对象。其中 malloc 分配指定字节数的存储区,其中的初始值不确定;calloc 为指定数量指定长度的对象分配存储空间,其中的每一位都初始化为 0;realloc 可增加或减少以前分配区的长度。当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定。realloc 的 newsize 参数是存储区的新长度,当 ptr 是空指针时,realloc 的功能就如同 malloc。free 函数可用来释放 ptr 指向的存储空间,以便以后再分配。
这些分配例程通常用 sbrk 系统调用实现,该系统调用可以扩充或缩小进程的堆。但是大多数 malloc 和 free 的实现都不减小进程的存储空间。释放的空间可供以后再分配,但将它们保持在 malloc 池中而不返回给内核。一般所分配的存储空间都比所要求的要稍大一些,额外的空间会用来记录管理信息,比如分配块的长度、指向下一个分配块的指针等。因此在动态分配的缓冲区前或后进行写操作极有可能会改写另一块的管理记录信息。另外,释放一个已经释放了的块或者释放不是由 alloc 类函数分配的块等都有可能导致致命性的错误。而如果不释放不再使用的空间,则会使进程地址空间长度慢慢增加,直至不再有空闲空间,这被称为泄漏(leakage)。这会导致过度的换页开销,从而造成性能下降。
每个程序都接收到一张环境表,它是一个字符指针数组,其中每个指针都包含一个指向以 null 结束的 C 字符串的地址,全局变量“extern char **environ”则包含了该指针数组的地址,通常称 environ 为环境指针,指针数组为环境表,其中各指针指向的字符串为环境字符串。例如,一个以 5 个字符串组成的环境看起来大致如下图所示。
main 函数在大多数 UNIX 系统上的原型是这样的:
int main(int argc, char **argv[], char *envp[]);
其中第三个就是环境表地址。不过因为该参数比起 environ 也没有带来多少益处,因此 POSIX.1 也规定应使用 environ 而不使用第 3 个参数。通常用 getenv 和 putenv 等函数来访问和设置特定的环境变量,而不是直接使用 environ,但如果要查看整个环境,则必须使用 environ 指针。
#include <stdlib.h> char *getenv(const char *name); /* 返回值:指向与 name 关联的 value 的指针;未找到就返回 NULL */ int putenv(char *str); /* 返回值:若成功,返回 0;否则,返回非 0 */ int setenv(const char *name, const char *value, int rewrite); int unsetenv(const char *name); /* 两个函数返回值:若成功,返回 0;否则,返回 -1 */
其中:
* getenv 是用来获取某个环境变量的值。
* putenv 取形式为“name=value”的字符串,将其放到环境表中。如果 name 已经存在,则先删除其原来的定义。
* setenv 将 name 设置为 value。如果 name 已经存在,则根据参数 rewrite 的值为 0与否来决定是否覆盖原来的定义。
* unsetenv 删除 name 的定义,即使不存在也不算出错。
注意,putenv 和 setenv 的差别在于:putenv 可以自由地将传递给它的参数字符串直接放到环境中(但不应该将存放在栈中的字符串传给它);setenv 必须分配存储空间,以便依据其参数创建 name=value 字符串。
了解这些函数是如何修改环境表也是非常有益的。其中,环境表和环境字符串通常存放在进程存储空间的顶部(栈之上)。删除一个字符串时只需要在环境表中找到该指针,然后将所有后续指针都向环境表首部顺次移动一个位置。但是增加一个字符串或修改一个现有的字符串就困难得多。因为环境表和环境字符串通常占用的是进程地址空间的顶部,所以它不能再向高地址方向扩展,同时也不能移动在它之下的各栈帧。两者结合使得该空间的长度不能再增加。
(1)当修改一个现有的 name 时:
a. 如果新 value 的长度不大于现有的长度,则只要复制新字符串到原字符串所在的空间即可。
b. 如果新 value 的长度大于原长度,则必须调用 malloc 为新字符串分配空间,然后将新字符串复制到该空间,接着使环境表中的针对 name 的指针指向新分配区。
(2)当增加一个新的 name 时就复杂多了。必须先调用 malloc 为 name=value 字符串分配空间,然后将该字符串复制到此空间中。
a. 如果这是第一次增加一个新的 name,则必须调用 malloc 为新的指针表分配空间。接着,将原来的环境表复制到新分配区,并将指向新 name=value 字符串的指针存放在该指针表的表尾,然后又将一个空指针存放在其后。最后使 environ 指向新指针表。如果原来的环境表位于栈顶之上,那么必须将此表移至堆中。但是此表中的大多数指针仍指向栈顶之上的各 name=value 字符串。
b. 如果这不是第一次增加一个新的 name,则说明之前已在堆中为环境表分配了空间,所以只要调用 relloc,以分配比原空间多存放一个指针的空间,然后将指向新 name=value 的字符串的指针存放在该表表尾,后面跟着一个空指针。
发表评论
-
打开伪终端设备
2018-07-09 20:50 1248在伪终端概述一节中已对 PTY进行了初步的介绍。尽管 ... -
伪终端概述
2018-06-02 11:05 1533伪终端就是指,一个应用程序看上去像一个终端,但事实上它 ... -
终端窗口大小和 termcap
2018-05-29 22:39 791多数 UNIX 系统都提供了一种跟踪当前终端窗口大小的 ... -
终端规范模式和非规范模式
2018-05-29 00:25 937终端规范模式很简单:发一个读请求,当一行已经输入后,终 ... -
终端标识
2018-05-23 11:18 566尽管控制终端的名字在多数 UNIX 系统上都是 /de ... -
波特率和行控制函数
2018-05-22 07:53 933虽然大多数终端设 ... -
终端属性和选项标志
2018-05-20 07:40 706tcgetattr 和 tcsetattr ... -
终端特殊输入字符
2018-05-17 06:33 807终端支持下表所示的特殊输入字符。 为了更改 ... -
终端 I/O 综述
2018-05-10 07:56 432终端设备可认为是由内核中的终端驱动程序控制的。每个终端 ... -
POSIX 信号量
2018-05-09 00:03 575在XSI IPC通信之信 ... -
XSI IPC 通信之共享存储
2018-04-25 07:18 943在XSI IPC通信之消息队列和XSI IPC通信之信 ... -
XSI IPC通信之信号量
2018-04-17 23:38 612在XSI IPC通信之消 ... -
XSI IPC通信之消息队列
2018-04-15 10:54 490消息队列是消息的链接表,存储在内核中,由消息队列标识符 ... -
XSI IPC 相似特征介绍
2018-02-08 23:48 479有 3 种称作 XSI IPC ... -
IPC 通信之 FIFO
2018-02-06 22:55 412FIFO 也被称为命名管道,未命名的管道只能在两个相关 ... -
IPC 通信之管道
2018-01-30 22:22 383管道是 UNIX 系统 IPC 的最古老但也是最常用的 ... -
readv/writev 函数及存储映射 I/O
2018-01-19 00:57 876readv 和 writev 函数可用于在一次函数调用 ... -
POSIX 异步 I/O
2018-01-16 21:33 449POSIX 异步 I/O 接口为对不同类型的文件进行异 ... -
fcntl 记录锁
2018-01-06 23:48 584记录锁的功能是:当有进程正在读或修改文件的某个部分时, ... -
守护进程惯例
2018-01-06 23:52 432UNIX 系统中,守护进程遵循下列通用惯例。 ...
相关推荐
### Linux下Eclipse调试C语言简介及环境配置 #### 一、引言 随着软件开发技术的不断发展,高效的集成开发环境(IDE)成为了提高开发效率的关键。对于C语言开发者而言,在Linux环境下寻找合适的IDE尤为重要。本文将...
这里可能涉及到短作业优先、优先级调度、轮转调度等算法的实现,展示了如何模拟多任务环境。 5. **迷宫**: - 解迷宫问题通常用到图论和搜索算法,比如深度优先搜索(DFS)或广度优先搜索(BFS)。这体现了C语言在解决...
本资源“C语言基础Linux环境.rar”聚焦于在Linux环境下学习和使用C语言的基础知识。Linux作为开源操作系统,提供了丰富的工具和资源,是学习和实践C语言的理想平台。 首先,让我们深入了解C语言的基础概念。C语言是...
"C语言实现的文件存储引擎"项目,结合了C语言的高效与灵活,利用高级编程思想来设计和实现了一个高效的数据存储解决方案。 首先,C语言作为底层编程语言,具有运行速度快、对硬件资源控制力强的特点。它能够直接...
"Linux环境C语言编程入门"这本书籍旨在引导初学者掌握在Linux系统中编写、编译和调试C程序的基本技巧。 首先,C语言是一种强大的编程语言,它简洁、高效,且提供了对底层硬件的直接访问,因此在系统级编程中广泛...
本文主要探讨了在ARM Developer Suite 1.2 (ADS1.2) 集成开发环境下,如何使用C语言编写ARM程序,并重点讨论了C程序运行环境初始化过程中的一些关键问题,特别是针对全局变量初始化的细节,以及对变量存储空间分配的...
在C语言环境下,可以利用结构体(struct)来定义学生记录,包括学生的姓名、学号和各科成绩。通过函数实现对这些数据的操作,例如添加新记录、查找特定学生的成绩、计算平均分等功能。这样的系统对于教学管理非常...
在C语言中实现图形环境下的分数多项式显示是一项有趣且具有挑战性的任务,它涉及到基本的C编程技术、字符处理以及简单的图形界面设计。这里我们将深入探讨如何在DOS环境下,利用C语言来创建一个程序,使分数和多项式...
标题中的“二级C语言环境 软盘版”指的是用于二级计算机考试的C语言编程环境,这个环境通常包含了编译器、调试器等必要的工具,是考生进行C语言编程学习和考试的重要平台。在早期的计算机系统中,软盘是常见的存储...
通过C语言编程,该系统能够实现数据采集、处理、存储以及远程监控,同时,配合微信小程序,用户可以随时随地查看猪舍环境状况,实现智能农业的愿景。 二、系统架构 1. 数据采集模块:利用各种传感器(如温湿度...
资源名称:Linux系统纯C语言开发网络游戏存储引擎 教程内容:[第1节] 1-1-1为公司搭建Linux下的c开发环境[第2节] 1-1-2为公司搭建Linux下的c开发环境[第3节] 2-1-1C语言数据类型[第4节] 2-1-2数据的快速引用[第5节]...
VC++6.0是Microsoft Visual C++的一个经典版本,它为开发人员提供了集成的开发环境,支持C和C++语言。在这个环境中,我们可以创建、编译和调试C语言代码,以构建各种应用程序,包括我们的迷宫程序。 首先,我们来看...
本教程《C语言程序设计(Visual C++6.0环境)》旨在帮助初学者理解C语言的基本概念,并学会在Visual C++6.0环境下编写和运行C程序。C语言的基础包括以下几个方面: 1. **变量与数据类型**:C语言中的基本数据类型有...
标题中的"C语言在WIN32环境下做的拼图游戏"指的是使用C语言编程,在Windows操作系统(具体为WIN32 API)平台上开发的一款拼图游戏。这种游戏通常涉及到图像处理、用户交互以及逻辑算法的设计。 首先,我们需要理解...
【FlyDB:C语言实现的键值存储系统】 FlyDB是一个用C语言编写的轻量级键值(Key-Value)存储系统,它为开发者提供了一种高效、简单的方式来存储和检索数据。作为NoSQL数据库的一种,FlyDB不遵循传统的表格型数据库...
电话号码存储系统是一种基于C语言实现的简单数据库应用,它主要用于管理和存储个人或组织的电话号码。在本文中,我们将深入探讨这个系统的核心概念、设计思路以及C语言在实现此类系统时的关键技术。 首先,C语言是...
C语言上机环境与上机调试 本节课主要介绍C语言的上机环境和上机调试的基本概念...通过学习本节课,学生将掌握C语言上机环境和上机调试的基本操作和概念,并能够独立上机调试简单的C程序,为后续的C语言学习奠定基础。
《Linux环境下C语言实现的俄罗斯方块》 在信息技术领域,C语言因其高效、底层以及灵活性,被广泛用于系统编程和游戏开发。本项目“Linux环境下C语言实现的俄罗斯方块”就是一个很好的实例,它展示了如何在Linux操作...
《C语言程序设计(Visual C++ 6.0环境)》是一本专为学习C语言编程的初学者设计的教程,特别强调在Visual C++ 6.0集成开发环境中进行实践操作。C语言是一种强大的、低级的编程语言,被广泛应用于系统编程、嵌入式...
本主题将深入探讨如何使用C语言实现稀疏矩阵的三元组压缩存储以及快速转置。 **稀疏矩阵的三元组压缩存储** 稀疏矩阵的三元组压缩存储方式是将非零元素按照行优先或列优先的顺序存储在一个二维数组中,每个元素...