一、引言
I、程序的内存分配
一个由C/C++编译的程序的存储空间布局分为以下几个部分:
数据段,代码段,堆栈段。
- 1 初始化的数据
通常将此段称为数据段.data,它包含了程序中需赋初值的变量。
初始化的全局变量和静态变量存放在这里。
例如,C程序中任何函数之外的说明:int maxcount = 99; 使此变量以初值存放在初始化数据段中。
- 2 非初始化数据段
通常将此段称为.bss段,这一名称来源于早期汇编程序的一个操作符,意思是“block started by symbol(由符号开始的块)”,
未初始化的全局变量和静态变量存放在这里。
在程序开始执行之前,内核将此段初始化为0。
函数外的说明:long sum[1000] ; 使此变量存放在非初始化数据段中。
- 3 代码段.Text
CPU执行的机器指令部分。通常,代码段是可共享的,所以即使是经常环境指针环境表环境字符串执行的程序(如文本编辑程序、C编译程序、shell等)在存储器中也只需有一个副本,另外,代码段常常是只读的,以防止程序由于意外事故而修改其自身的指令。
- 4 堆Heap
需要由程序员分配释放管理,一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。通常在堆中进行动态存储分配。
- 5 栈Stack
由编译器自动分配释放管理。
- 局部变量
- 每次函数调用时返回地址
- 调用者的环境信息(例如某些机器寄存器)都存放在栈中。
新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,C函数可以递归调用。其操作方式类似于数据结构中的栈。
![photo-of-rocks-piled-on-top-of-each-other]https://cdn.jsdelivr.net/gh/aemonair/ImageHosting/images/20200429120559_photo-of-rocks-piled-on-top-of-each-other.jpg)
II、数据结构.栈
** 栈(stack) **, 是一种线性存储结构,它有以下几个特点:
栈中数据是按照”后进先出(LIFO, Last In First Out)”方式进出栈的。
向栈中添加/删除数据时,只能从栈顶(top)进行操作。
为什么在程序内存分配时会用栈,想必一定有道理。
我们看,身为栈式结构,我们可以用它来做什么呢?
首先,函数调用栈一般是从高地质向低地址增长的,栈顶为内存的低地址,栈底为内存的高地址。
函数调用栈中存储的是数据的活动记录。活动记录是函数一些信息。
程序中的栈的应用—-递归调用
我们从主函数调用某子函数,向子函数传递参数,然后进入子函数,我们再次调用这个函数,依次类推。这个时候程序中的栈被经历了多次子函数的压入,在最近一次运行结束以后,栈中的值依次弹出,首先弹出的是最后一个元素,然后以此类推。
程序在递归调用期间不断的向栈中压入每一次函数调用的记录,在递归调用结束后再从栈弹出每一次的记录。
二、通用栈的定义及实现
I 、通用栈的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #ifndef _STACK_H_ #define _STACK_H_
#include "dlist.h"
typedef struct Stack { Dlist *dlist; //用我们之前实现的链表实现的栈 }Stack;
//接口哦 // //1.初始化 Stack *init_stack(void); //2.销毁 void destroy_stack(Stack **stack); //3.入栈 Boolean push_stack(Stack *stack, void *value); //4.出栈 Boolean pop_stack(Stack *stack); //5.得到栈顶元素 Boolean get_top_stack(Stack *stack, void **value); //6.判断栈是否为空 Boolean is_stack_empty(Stack *stack); //7.得到栈的元素个数 int get_stack_count(Stack *stack); // #endif
|
我们用来封装栈的链表接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #ifndef _DLIST_H_ #define _DLIST_H_
#define TRUE (1) #define FALSE (0) #define ZERO (0) #define ONLY_ONE (1) typedef unsigned char Boolean ; typedef void (*Print_func)(void *value);
void print_int(void *data);
typedef struct Dlist_node{ struct Dlist_node *prev; struct Dlist_node *next; //指向后一个节点 void *data; }Dlist_node;
//通用链表控制信息 typedef struct Dlist{ struct Dlist_node *head; //指向头结点 struct Dlist_node *tail; int count;
//这是一个指向某个需要被释放的数据域的函数指针 void (*free)(void *ptr); //比较节点数据域 Boolean (*match)(void *value1, void *value2); //拷贝节点数据域 void *(*copy_node)(void *value); }Dlist;
//1.双端链表的初始化 Dlist *init_dlist(void); //2.双端链表的销毁 void destroy_dlist(Dlist **dlist);//二重指针 //3.头部插入 Boolean push_front(Dlist *dlist, void *data); //4.尾部插入 Boolean push_back(Dlist *dlist, void *data); //5.头部删除 Boolean pop_front(Dlist *dlist); //6.尾部删除 Boolean pop_back(Dlist *dlist); //7.插入到当前节点前 Boolean insert_prev(Dlist *dlist, Dlist_node *node, void *value); //8.插入到当前节点后 Boolean insert_next(Dlist *dlist, Dlist_node *node, void *value); //9.删除某个节点 Boolean remove_dlist_node(Dlist *dlist, Dlist_node *node); //10.显示双端链表信息 void show_dlist(Dlist *dlist, Print_func print); //11.得到第一个节点数据域 Boolean get_front(Dlist *dlist, void **value); //12.得到最后一个节点数据域 Boolean get_tail(Dlist *dlist, void **value); //13.得到链表节点数量 int get_dlist_count(Dlist *dlist);
#endif
|
II、通用栈的接口实现
我们可以直接利用链表的接口进行封装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| #include <stdio.h> #include "stack.h" #include "tools.h" #include "dlist.h"
//接口实现哦 // //1.初始化 Stack *init_stack(void) { Stack *stack = (Stack *)Malloc(sizeof(Stack)); //初始化链表 stack->dlist = init_dlist();
return stack; } //2.销毁 void destroy_stack(Stack **stack) { if(stack == NULL || *stack == NULL) { return ; } // destroy_dlist(stack->dlist); destroy_dlist(&((*stack)->dlist)); free(*stack); *stack = NULL; } //3.入栈 Boolean push_stack(Stack *stack, void *value) { if(stack == NULL || value == NULL) { return FALSE; } return push_back(stack->dlist, value); } //4.出栈 Boolean pop_stack(Stack *stack) { if(stack == NULL ) { return FALSE; } return pop_back(stack->dlist);
} //5.得到栈顶元素 Boolean get_top_stack(Stack *stack, void **value) { /* if(stack == NULL || value == NULL) { return FALSE; } else { return get_tail(stack->dlist, value); } */ if(stack == NULL || is_stack_empty(stack)) { return FALSE; } if(value != NULL) { get_tail(stack->dlist, value); } } //6.判断栈是否为空 Boolean is_stack_empty(Stack *stack) { // return (get_dlist_count(stack->dlist) >= ZERO); // return get_stack_count(stack) == ZERO; } //7.得到栈的元素个数 int get_stack_count(Stack *stack) { if(stack == NULL) { return -1; } return get_dlist_count(stack->dlist); }
|
III、其他
我们将之前的dlist.c & dlist.h & tools.h & tools.c 都加入到我们的工作区内。
三、函数功能及实现
I、测试函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <stdio.h> #include "stack.h"
int main(int argc, char **argv) { int i = 0; int a[]={1,2,3,4,5}; void *value; int length = sizeof(a)/sizeof(a[0]);
Stack *stack = NULL; stack = init_stack(); for(i=0; i< length;++i) { push_stack(stack, &a[i]); }
Dlist_node *node = stack->dlist->head; for(i=0; i< length || node ;++i) { print_int(node->data); node = node ->next; } printf("\n"); get_top_stack(stack, &value); printf("Top:"); print_int(value);
printf("\n");
printf("the length:\n"); printf("%d \n",get_stack_count(stack));
for(i = 0; i < length ; ++i) { get_top_stack(stack, &value); pop_stack(stack); print_int(value); } printf("\n"); destroy_stack(&stack);
return 0; }
|
II、测试运行结果:
1 2 3 4 5 6 7 8 9 10 11 12
| root@aemonair:# cc.sh *.c Compiling ... -e CC dlist.c main.c stack.c tools.c -g -lpthread -e Completed . -e Fri Jun 17 23:24:38 CST 2016
root@aemonair:# ./dlist 1 2 3 4 5 Top:5 the length: 5 5 4 3 2 1
|
四、总结
我们主要不止是通过链表封装了栈,而是了解栈的运行原理。
以及,我们应该通过简单的数据结构中栈的操作对程序运行时堆栈的情况有所了解,由于是数据结构,我们不再多说,在以后我们将对程序运行时的分配情况进行更多的了解。