C语言进阶02-栈内存

本文介绍了计算机内存的地址与值的关系,讲解了栈内存的概念,特别是在函数调用中的作用。栈内存遵循后进先出原则,用于存储函数调用的返回位置、实参和局部变量。局部变量在函数调用结束后销毁。此外,文章还讨论了变量的地址、函数返回值、数组的声明和初始化,以及如何通过`&`获取变量地址。强调了不同函数中相同变量名不代表同一地址,因为每个函数有自己的栈帧。最后,总结了栈内存的重要性及其在函数调用过程中的应用。
摘要由CSDN通过智能技术生成

01

值和地址

程序和数据在计算机中是存储在一个叫存储器的地方. 没有存储器, 计算机就不能计算, 因为没地方计算(这不是废话吗). 像内存称为易失的存储器, 硬盘称为非易失的存储器. 其中SSD又叫闪存.

计算机的内存被编组成地址-值的关系对. 如: 学生的姓名和所在大学的院系之间的关系.

1、小明: 清华大学计算机系

2、小李: 北京大学计算机系

等等.

计算机内存中, 每个位置存储的不是0就是1, 如下面这样:

1、0存储在第1个位置.

2、0存储在第2个位置.

3、1存储在第3个位置.

4、0存储在第4个位置.

5、1存储在第5个位置.

我们暂且把数据大小放一边. 假定每一块数据占据着内存的一个单元. 操作系统保证任何内容都有一个唯一的正数地址. 地址不能为零或负数. NULL被定义为零地址, 表示是一个无效地址. 要记住计算机程序操作的所有内存位数的地址是不可能的. 计算机大牛们找到了一个好的解决办法: 创建标志符, 如counter或者sum来指代内存中的相关位. 若它们在程序运行期间发生变化, 这个标志符就叫作变量. 标志符对编写的计算机程序是有意义的, 编译器(如gcc)会把这些标志转化为地址. 最终的计算机程序会操作这些值, 并不会看到这些标志. 内存中只有地址和值. 请看标志和地址之间的关系:

源代码.c或.h文件

可执行程序
人类可读
通过编译器
计算机可读
标志符

地址
看代码:

int a = 5;
double b = 6.7;
char z = 'c'

标志符、地址和值可能看起来像这样:

标志符
地址

a
100
5

b
135
6.7

z
167
‘c’

我们只要下面的规则即可(其他的事情是操作系统和编译器干的事情. 有兴趣可以看看编译原理与linux内核源码, 至少三年起步入门吧.):

1、每个数据片段拥有一个唯一的地址.

2、地址不允许是0(NULL)或是负数.

3、编译器能够把标志符转换为地址.

02

计算机把内存分为三种类型:

1、栈内存.

2、堆内存.

3、程序内存.

1、2用来存储数据, 3、用来存储机器码.

我们先来介绍栈的概念. 英文叫"stack"在"小筑的数据结构与算法模块中"已经介绍过栈了, 这里在简单说下. 栈是一种"后进先出"的结构, 比如: 刷完碗, 把碗摞起来后, 要想使用最底下的碗, 就需要将上面的碗一个一个的拿走, 这个就是栈. "摞碗"称为入栈, "取碗"称为出栈. 是伟大的图灵提出的.栈内存严格地遵守着先入后出的原则. 当然也可以实现双向栈, 这里详说了.

03
调用栈
计算机如何使用栈内存呢? 看代码:


void fi(void)
//f1前面的void表示没有返回值.
//括号中的void表示没有参数.
{
  //.....
}
void f2(void)
{
  //一堆代码
  f1();
  //另一堆代码
}

当执行f2()时, 先运行"一堆代码", 然后运行"f1()", 跳入"f1()“中执行完成后, 执行"另一堆代码”. 执行顺序就是机器码被压入栈内存, "后进先出"的方式执行.

为什么栈内存"后进先出"这么重要呢? 栈内存存储着函数调用的倒序. 先调用f2(), 则f2()入栈, 后调用f1(),f1()入栈, 然后f1()出栈执行,记录f1()执行后的位置, f1()执行完后, 从记录的位置, f2()出栈接着执行.

调用栈的规则总结:

1、如果函数有实参, 那么实参是存储在返回位置之上的.

2、实参和返回位置共同构成了调用函数的栈帧.

3、当一个函数被调用时, 这条调用之后的行编号就被压入调用栈. 这个行编号就是"返回位置". 这是在被调用函数结束(即返回)之后程序继续执行的地方.

4、若相同的函数在不同行处被调用, 那么每个调用都有一个相应的返回位置(每个函数调用之后的那行).

5、当一个函数结束之后, 程序将从存储在调用栈顶部的行编号处继续执行. 调用栈顶部的内容就会被弹出.

04

局部变量

若函数有局部变量, 那么局部变量被存储在调用栈中. 看下面的例子:

void f1(int k, int m, int p)
{
  int t = k + m; //t, u为f1()中的局部变量, 随着f1()运行结束而销毁.
  int u = m * p;
}

void f2(void)
{
  f2(5, 11, -8);
}

如果函数有局部变量, 则局部变量被存储在实参之上. 局部变量总是存储在栈上, 函数调用期间一直存放在那里. 与它们的存在形成对比的是, "全局变量"在函数调用之间存在. 全局变量通常是在一个给出的源文件顶部进行指定, 任何函数都可以对它们进行读和写. 虽然C允许使用全局变量, 但我们要避免使用. 主要问题是全局变量可以在程序的任何位置发生改变. 当程序变得越来越大的时候, 要去追踪这些全局变量可能发生变化的位置就会变得越来越困难. 全局变量不建议, 但是全局常量是推荐经常使用的. 因为全局常量是不变的.

05

值地址

C语言中函数是可以返回值的, 看代码:

int f1(int k, int m)
{
  return (k + m);
}

void f2(void)
{
  int u;
  u = f1(7, 2);
}

f2中的局部变量u在f2的栈帧中, 没有被赋值, 因为C不初始化变量, 所以这里u可以存储任何int值. u的地址是在f1被调用之前存储在调用栈的. 这个地址就是值地址, 因为它是函数f1存储返回值的地址. 因此, 当f1的栈帧建立后, 就要为地址再加上一行, 它的值是u的地址.

关键词return可以出于两种不同的目的被使用:

1、如果void位于函数名前面, 此函数不返回任何值. 程序运行到return时函数停止, 程序从调用函数中返回地址继续.

2、如果不是void型, return会为在调用栈中由值地址给出的变量赋值.

只要记住碰到return后程序就终止执行了.

06

数组

看代码:

int arr[5];

表示声明了一个有5个整型元素的数组. 下标从0开始, 到4结束. 数组中的元素地址总是连续的. 如果一个数组的元素没有被初始化, 那么其值就是未占用的.

初始化一个数组:

int arr[5] = {-31, 52, 65, 49, -18};

全部初始化为0:

int arr[5] = {0};

不给出长度的情况下创建一个数组:

int arr[] = {-31, 52, 65, 49, -18}; 表示长度为5的数组.

07
获取地址
可以通过&来获取变量地址. 使用printf("%p", &a);来打印变量的地址. 看代码:

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

int main(int argc, char **argv)
{
    int a = 5;
    int c = 17;
    printf("a's address is %p, c's address is %p\n", &a, &c);
    return EXIT_SUCCESS;
}
# 结果为:
a's address is 0xffffcbec, c's address is 0xffffcbe8

08

可见度
当一个函数被调用的时候, 一个新的栈帧就被压入调用栈. 函数只能看到它自己的栈帧. 看代码:

int f1(int k, int m)             
{
  return (k + m);
}

void f2(void)
{
  int a = 5;
  int b = 6;
  int u;
  u = f1(a + 3, b - 4);
}

int f1(int a, int b)
{
  return (a + b)
}

代码中两个f1是完全相同的. 只不过将实参k,m换成了a, b, 这里强调了f2中的a,b与f1中的a,b地址是不同的. 计算机不知道标志符, 只知道使用地址和值.

09
总结

咱们介绍了栈内存的概念, 它在函数被调用时会用到. 栈内存为每一个函数存储返回位置, 值地址, 实参和局部变量.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

算法小筑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值