C语言_内存之栈、大小端

1. 栈

 

通常我们说的堆栈有两种形式,即:

  • 数据结构场景下,堆与栈表示两种常用的数据结构
  • 程序内存布局场景下,堆与栈表示两种内存管理方式

这两种栈在含义上略有不同,但是其核心思想和理念是相同的,即先进后出,如下图所示:

 

1.1 数据结构中的栈

        具有先进后出的性质,有两种实现方式,一种是静态栈,一种是动态栈

       静态栈是一种连续储存的数据结构(数组实现),动态栈是非连续存储的数据结构(链表实现)。通常在数据结构中的操作有入栈(压栈),出栈,初始化,清空栈。想要读取栈

中的某个元素,就是将其之间的所有元素出栈才能完成。

1.1.1  静态栈

静态栈的顺序存储方式使它成为一种运算受限的顺序表,因为栈的大小是固定的,静态栈的示意图如下:

利用一维数组依次从栈底存放,由于对栈的操作只能在栈顶进行,所以必须要有栈顶的标志。 首先,定义一个有固定容量的栈空间,其结构描述为

#define maxsize 100
typedef struct 
{
    /* 栈顶标志 */
    int top;
    /* 栈中的结点存储在一维数组中 */
    int data[maxsize];
}stack;

对静态栈的基本操作为置空栈,判空栈,返回栈顶数据,入栈,出栈等

/* -置空栈 */
void SetEmpty(stack var_s)
{
    var_s.top = -1;
}

/* -判空栈 */
int IsEmpty(stack var_s)
{
    if (var_s.top == -1)
    {
        return TRUE;    // 空栈返回真
    }
    else
    {
        return FALSE;   // 非空返回假
    }
}

/* 取栈顶结点值 */
int GetTopValue(stack var_s)
{
    if(IsEmpty(var_s))
    {
        printf("当前栈为空\n");
    }
    else
    {
       return var_s.data[var_s.top];
    }
}

/* -入栈 */
int Push(stack var_s,int var_data)
{
    if (var_s.top == maxsize-1)
    {
        printf("栈已满!\n");
        return FALSE;
    }
    else
    {
        var_s.data[++var_s.top] = var_data;
        return TRUE;
    }  
}

/* -出栈 */
int Pop(stack var_s)
{
    if(IsEmpty(var_s))
    {
        printf("当前栈为空\n");
    }
    else
    {
        return var_s[var_s.top--];
    }
}

简单的举个例子,利用栈的数据结构可以实现逆序,例如可以实现十进制转化为二进制

1.1.2  动态栈

动态栈简称链表栈,栈中每个结点都是动态分配的,入栈相当于在单链表的尾插,出栈相当于单链表的尾删,可以理解为尾部就是栈顶。(用visio画图太麻烦了,手画了一下,区分头指针与头结点,在链表中通常的输入参数为头指针

不理解链表的同学,请网上自行查找相关资料,这里直接上代码(静态栈的代码,我没验证,动态栈的我验证了)。

link_stack.h为下面代码

#include <stdio.h>
#include <stdlib.h>

/* -SECTION_1 自定义类型 */
#define uint_8	unsigned char
#define uint_16 unsigned int
#define uint_32 unsigned long
#define  TRUE	1
#define	 FALSE	0


/* -SECTION_2 动态栈相关定义 */
typedef struct node
{
	uint_16 data;  // 结点中的数据
	struct node *pnext;   // 结点中的指针域
}link_stack;


extern link_stack *init_stack(link_stack *phead);
extern link_stack * set_null(link_stack *phead);
extern uint_8 is_null(link_stack *phead);
extern void showall(link_stack *phead);

 link_stack.c为下面代码

#include <stdio.h>
#include <stdlib.h>
#include "list_stack.h"

//link_stack *phead = NULL;    // 头指针

/* -初始化 */
link_stack* init_stack(link_stack *phead)
{
	return NULL;
}

/* -置空,输入参数:头指针(头部删除不符合动态栈的描述,仅仅是为了置空) */
link_stack * set_null(link_stack *phead)
{
	link_stack *p1=phead,*p2=NULL;

	while(p1 != NULL)
	{
		p2 = p1;
		p1 = p1->pnext; 
		free(p2);
	}
	return p1;
}
//void set_null(link_stack **phead)  // 也可以使用二级指针
//{
//	link_stack *p1=NULL;
//
//	while(*phead != NULL)
//	{
//		p1 = *phead;
//		*phead = (*phead)->pnext; 
//		free(p1);
//	}
//}


/* -判空 */
uint_8 is_null(link_stack *phead)
{
	if (phead == NULL)
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

/* -入栈,因为要改变头结点,所以返回的是指针 */
link_stack* push(link_stack *phead,uint_16 var_data)
{
	link_stack *pnewnode = (link_stack*)malloc(sizeof(link_stack));

	pnewnode->data = var_data;
	pnewnode->pnext = NULL;
	if(is_null(phead))
	{
		phead = pnewnode;
	}
	else
	{
		link_stack *p = phead;
		while(p->pnext != NULL )
		{
			p = p->pnext; 
		}
		p->pnext = pnewnode;
	}

	return phead;
}

/* -出栈 */
link_stack* pop(link_stack *phead,link_stack *p_out_node)
{
	if (is_null(phead))
	{
		printf("空栈\n");
		return NULL;
	}
	else if(phead->pnext == NULL)
	{
		p_out_node->data = phead->data;
		free(phead);
		phead = NULL;
		return phead;
	}
	else
	{
		link_stack *p =phead;
		while (p->pnext->pnext != NULL)
		{
			p = p->pnext;
		}
		p_out_node->data = p->pnext->data;
		free(p->pnext);
		p->pnext = NULL;
		return phead;  
	}
}

/*****
 ** 函 数 名:显示结点的所有数据
 ** 输入参数:
 **		1,ST *phead,头结点,即第一个结点的地址
 ** 输出参数:无
 ** 备	 注:无
*****/
void showall(link_stack *phead)
{
	uint_16 i = 1;
	/* 遍历所有结点 */
	while(phead != NULL)
	{
		printf("\n结点_%d,	数据为%d",i,phead->data);
		printf("\t本结点地址%p,下一结点地址%p",phead,phead->pnext);
		phead = phead->pnext;
		i++;
	}
}

main.c为

void test_1(void)
{
	/* -初始化 */
	link_stack *head = NULL; //头指针
    //对数据操作有分配空间
	link_stack *pop_data = (link_stack*)malloc(sizeof(link_stack)); 
	head = init_stack(head);

	head = push(head,1);
	head = push(head,2);
	head = push(head,3);
	head = push(head,4);

	/* 显示所有结点 */
	showall(head);
	printf("\n\n");
	//pop_data = head;
	head = pop(head,pop_data);
	showall(head);
	printf("\n\n");
	head = set_null(head);
	/* 显示所有结点 */
	showall(head);

}

/* -链表栈练习 */
void main()
{
	test_1();

	system("pause");	// 暂停
}

 写代码的时候,因为输入参数的头指针属于一级指针,在函数内部尽管我改变了指针的指向,但是实参并没有发生改变,因此有两种方法可以做,一种是属于二级指针(一级指针的地址),一直中返回地址,然后直接改变实参的指向。一级指针和二级指针做形参可以参考我的博客:c语言_指针的理解

 

1.2 内存管理的栈

栈的生成方式是从高地址到低地址,即先分配的变量存在高地址,后分配的变量在高地址,请允许我测试时,用递归小浪一下,

void stackDrec()
{
	static char *addr = NULL;

	char dummy;
	if (addr == NULL)
	{
		addr = &dummy;
		stackDrec();
	}
	else
	{
		if (&dummy > addr)
		{
			printf("向高地址方向生长,dummy: %d,addr: %d\n",&dummy,addr);
		}
		else
		{
			printf("向低地址方向生长,dummy:%d,addr:%d\n", &dummy, addr);
		}
	}
}


void main()
{
	stackDrec();

	system("pause");
}

所以,我们当我们写程序时,例如我们定义了N个局部变量;

void test(void)
{
    int i_1;    // 第一个入栈,在栈底
    int i_2;    // 第2个入栈
    int i_3;    // 第3个入栈
        .
        .
        .
    int i_n;     // 第n个入栈,当程序运行完成,第一个被释放,即出栈
}

典型的存储结构如图所示:

2. 大小端

        在我们的计算机系统中,数据的存储是以字节为单位的,每个地址单元都对应着一个字节,所以我们可以理解成为一个字节的内存空间绑定一个地址。

        在C语言中,我们定义的变量存储在内存中,如我们定义int i =1,编译器会在栈中分配一段4字节的内存空间给这个局部变量使用,我们不禁会想,在内存中存储模型是下面两者中的哪一个呢?

由此引入了大小端的问题,大端就是低字节排放在内存的低端,高位字节排放在内存的高端; Big Endian就是高位字节排放在内存的低端,低位字节排放在内存的高端。大小端是由处理器决定的。

/* -内存管理 */
void main()
{
	int a =0xaabbccdd;

	printf("%p",&a);

	system("pause");	// 暂停
}

可以看到我们显示的结果是小端

        有时候我们用a的首地址&a对变量a进行操作,我们可能要问a的首地址是指-0x0032FE28还是0x0032FE2B呢?要记住一个结论:无论为大端、小端,变量的首地址都为低内存地址

        故当我们定义变量时,变量的首地址是低内存地址,所以通常面试中,我们可以通过首地址(指针)来测试大小端的代码:

int Check_sys()
{
    int a = 1;
    //char* p = (char*)&a;
    //return *p;  //大端返回0,小端返回1
    //还可以写成下面的方式:
    return *(char*)&a;
}
int main()
{
    int ret=Check_sys();    //写一个测试的函数
    if (1 == ret)
    {
        printf("当前模式为小端存储\n");
    }
    else
    {
        printf("当前模式为大端存储\n");
    }
    return 0;
}

还有一种方法是,根据共用体来测试大小端,代码为:

int checkCPU()
{
    union w
    {
        int a;
        char b;
    }c;
    c.a = 1;
    return (c.b == 1); // 小端返回TRUE,大端返回FALSE
}

附加:

       在 C 语言中,sizeof() 是一个判断数据类型或者表达式长度的运算符。不如下面的代码,我们定义int a,那么编译器会自动根据我们定义的类型给我们分配4字节的内存空间,这是编译器自动完成的,不需要我们操作。这里需要注意的一点是&a分配的内存大小一直是4字节。

void main(void)
{
	/*int a;*/
	double a;
	/*char a; */
	double* p = &a; // p的类型为double*型的

	printf("%d\n", sizeof(a));  // 结果依次为 4,8,1
	printf("%d\n", sizeof(&a)); // 结果依次为 4,4,4
	printf("%d\n", sizeof(p));  // 结果依次为 4,4,4,
	system("pause");
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值