C语言--调用堆栈及栈区内存使用

目录

调用堆栈

调用堆栈是什么 

 调用堆栈的使用

栈区内存使用 

 通过调试观察内部运行

 提示


前言:这里我们的编写环境是VS2019及VS2022。

调用堆栈

  • 调用堆栈是什么 

概念:调用堆栈反映的是函数的调用逻辑 

在哪:首先我们要找到调用堆栈在哪里,而找到调用堆栈的前提是我们的程序已经是调试状态了。 

然后再根据下图,找到调用堆栈:

  •  调用堆栈的使用

 首先,我们先来一段简单的函数调用,用来直观感受:

void test2()
{
	printf("hehe\n");
}
void test1()
{
	test2();
}
void test()
{
	test1();
}
int main()
{
	test();
	return 0;
}

接下来,我们启动调试,然后找到调用堆栈,观察函数调用的细节:

 

 然后会出现下面的场景:

 这代表的意思是:由上到下依次发生函数调用

 这里我们发现其实main()函数也是被其他函数调用的(先不做过多解释)

再接着我们按F11继续调试:

 当调用完所有函数后,再依次从列表中消失,直到整个程序结束

 

 补充:栈是一种数据结构,特点是:元素后进先出 (可从上图每次都是顶上的元素先出去得直观看到)

栈区内存使用 

下面看代码:

#include <stdio.h>
//前提:VS,x86,debug模式下验证
int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i <= 12; i++)
    {
        arr[i] = 0;
        printf("hehe\n");
    }
    return 0;
}

该代码执行结果为:hehe(死循环)

为什么呢? 

  •  通过调试观察内部运行

这里我们可以看到初始化后的数组: 

 当数组下标为9的元素的值也改为0时:

开始发生越界:

 但当i=12时,奇怪的事情发生了:

我们再调试一步:

 此时我们要思考是否变量i和arr[12]使用的同一块空间。

 

 通过进一步调试可以看出,它们的地址相同,由此得出二者使用的是同一块空间

 下面进行画图分析一下产生该现象的原因,此前我们需要明白两个知识点:

知识点1.

栈区上内存使用习惯是由高地址向低地址处使用

知识点2.

数组随着下标的增长,地址是由低到高变化的

再结合下图: 

 注意:

  1. 如果我们将循环判断条件改为i<=11或i<=10,程序在运行时会报错,原因是数组越界访问
  2. 而循环判断条件i<=12不报错的原因是:数组元素覆盖到i,该程序进行死循环,即使已经越界也没空给你报出这个错误
  •  提示

我们不要因为害怕i被数组覆盖到就将变量i创建到数组的后面去:

 最本质的原因是数组越界访问,而把问题归结到创建变量的顺序上就属于本末倒置了。

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
在 C 语言中,可以使用堆栈(也称为栈)来实现函数的嵌套调用和递归调用堆栈是一种数据结构,具有先进后出的特性,类似于一叠盘子,后放入的盘子必须先取出来。 在函数调用时,每次调用都会将当前函数的返回地址、参数和局部变量等信息压入堆栈中,然后跳转到被调用函数的入口地址执行。当被调用函数执行完毕后,会从堆栈中弹出之前保存的信息,恢复到调用该函数之前的执行状态,然后继续执行原来的函数。 下面是一个使用堆栈实现函数嵌套调用的示例代码: ```c #include <stdio.h> #include <stdlib.h> #define STACK_SIZE 100 // 定义堆栈结构体 typedef struct { int top; int data[STACK_SIZE]; } Stack; // 初始化堆栈 void initStack(Stack *stack) { stack->top = -1; } // 压入元素 void push(Stack *stack, int value) { if (stack->top >= STACK_SIZE - 1) { printf("Stack overflow!\n"); exit(EXIT_FAILURE); } stack->data[++stack->top] = value; } // 弹出元素 int pop(Stack *stack) { if (stack->top < 0) { printf("Stack underflow!\n"); exit(EXIT_FAILURE); } return stack->data[stack->top--]; } // 判断堆栈是否为空 int isEmpty(Stack *stack) { return stack->top < 0; } // 函数嵌套调用 int foo(int x) { printf("enter foo(%d)\n", x); int y = x + 1; int z = bar(y); printf("exit foo(%d)\n", x); return z; } int bar(int x) { printf("enter bar(%d)\n", x); int y = x * 2; printf("exit bar(%d)\n", x); return y; } int main() { Stack stack; initStack(&stack); push(&stack, 1); push(&stack, 2); push(&stack, 3); while (!isEmpty(&stack)) { int value = pop(&stack); printf("%d\n", value); } int result = foo(10); printf("result=%d\n", result); return 0; } ``` 在上面的示例代码中,我们定义了一个堆栈结构体,包含一个整型数组和一个指向栈顶的指针。然后实现了初始化堆栈、压入元素、弹出元素和判断堆栈是否为空等基本操作。 在 `main` 函数中,我们首先演示了如何使用堆栈来存储一些数据,并依次弹出输出。然后演示了函数嵌套调用的过程,其中函数 `foo` 调用了函数 `bar`。每次调用函数时,我们将当前函数的返回地址和参数压入堆栈中,然后跳转到被调用函数的入口地址执行。当被调用函数执行完毕后,我们从堆栈中弹出之前保存的信息,恢复到调用该函数之前的执行状态,然后继续执行原来的函数。 类似地,我们可以使用堆栈实现递归调用。下面是一个使用堆栈实现阶乘函数的示例代码: ```c #include <stdio.h> #include <stdlib.h> #define STACK_SIZE 100 // 定义堆栈结构体 typedef struct { int top; int data[STACK_SIZE]; } Stack; // 初始化堆栈 void initStack(Stack *stack) { stack->top = -1; } // 压入元素 void push(Stack *stack, int value) { if (stack->top >= STACK_SIZE - 1) { printf("Stack overflow!\n"); exit(EXIT_FAILURE); } stack->data[++stack->top] = value; } // 弹出元素 int pop(Stack *stack) { if (stack->top < 0) { printf("Stack underflow!\n"); exit(EXIT_FAILURE); } return stack->data[stack->top--]; } // 判断堆栈是否为空 int isEmpty(Stack *stack) { return stack->top < 0; } // 阶乘函数 int factorial(int n) { Stack stack; initStack(&stack); push(&stack, n); int result = 1; while (!isEmpty(&stack)) { int value = pop(&stack); if (value <= 1) { break; } result *= value; push(&stack, value - 1); } return result; } int main() { int result = factorial(5); printf("result=%d\n", result); return 0; } ``` 在上面的示例代码中,我们定义了一个阶乘函数 `factorial`,使用堆栈实现了递归调用。在递归调用过程中,每次调用函数时,我们将当前函数的返回地址和参数压入堆栈中,然后跳转到被调用函数的入口地址执行。当被调用函数执行完毕后,我们从堆栈中弹出之前保存的信息,恢复到调用该函数之前的执行状态,然后继续执行原来的函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡闹的猫.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值