C语言 -- 函数内局部数组

简介: 直接看这个例子 void test() {     char b[25];     printf("%s\n",b);     b[0]= 'a';     b[1] = 'b';     b[2] = 'c';     b[3]= '\0';     printf("%s\n",

直接看这个例子

void test()
{
    char b[25];
    printf("%s\n",b);
    b[0]= 'a';
    b[1] = 'b';
    b[2] = 'c';
    b[3]= '\0';
    printf("%s\n",b);
}
对于上面的test函数, 如果连续调用两次, 会得到什么样的输出

答案是:

???

abc

abc

abc

这是我实际测出的值, 我当时很迷惑, 为什么b是局部数组, 当函数结束时这部分内存就被释放了, 而第二次调用test时, b数组的值仍然是abc.

可能有人说局部数组是存放在静态存储区的..., 对吗?

好, 下面就来解答这个问题,

编译时程序分为3段:text段,data段,bss段

text段:就是放程序代码的,编译时确定,只读,

data段:存放在编译阶段(而非运行时)就能确定的数据,可读可写,就是通常所说的静态存储区,赋了初值的全局变量和静态变量存放在这个区域,常量也存放在这个区域

bss段:定义而没有赋初值的全局变量和静态变量,放在这个区域

 

运行时程序还需要的区域

堆栈 (stack): stack就是存放局部变量, 临时变量的, 如调用函数时, 存放函数的局部变量

堆 (heap):heap就是用来存放动态分配的内存的, 即是通过malloc或是new分配的

 

明确一点, 局部变量一定是存放在堆栈的, 不可能放在静态区, 除非你加上static, 那么为什么会出现上面的现象?

透过现象看本质...

首先要理解堆栈的实现, 对于我们说的堆栈压入, 抛出操作, 并不是象我们想的那样实现的, 抛出后内容就不存在了

堆栈的操作就是通过移动栈顶指针实现的, 栈顶指针和栈底指针之间就是当前堆栈的大小

所以第一次调用test时, 分配了25字节的堆栈空间, 此时栈顶指针加25, 你对前4个字节赋值, 当函数结束时, 进行抛出操作, 即栈顶指针减25

但要注意, 此时系统并不会自动清空这25字节, 即他们仍然保留原来的内容.

第二次调用test时, 仍然分配了25字节的堆栈空间, 此时栈顶指针再加25, 其实这只是巧合, 分配了和第一次调用相同的空间, 并且内容都一样.

这就解释了我们上面说的那个问题.

想要验证很容易, 只需要在两次test调用中, 调用一次其他函数, 如果该函数修改了那段堆栈, 那么在第二次test中,就不会看到abc的初始值了. 这里绝对不要误认为局部数组是放在静态存储区的......千万不能被现象所迷惑...

 

总结

1. 通过malloc, 或局部数组分配的堆空间或堆栈空间, 首先用memset清0, 这是很多新手会忽视的, 这个很重要, 刚分配的空间的内容是不可预知的, 不清空很容易会影响程序的逻辑.

2. 在函数中不应该直接创建大的数组, 因为局部变量是分配在堆栈上的, 这样做, 不但效率不高, 而且会导致堆栈溢出, 堆栈空间是有限的.

    对于大的数组, 应该通过malloc分配堆空间来解决.


本文章摘自博客园,原文发布日期:2011-07-05

目录
相关文章
|
28天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
31 3
|
19天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
32 10
|
12天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
18天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
44 7
|
18天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
25 4
|
19天前
|
存储 编译器 C语言
【c语言】数组
本文介绍了数组的基本概念及一维和二维数组的创建、初始化、使用方法及其在内存中的存储形式。一维数组通过下标访问元素,支持初始化和动态输入输出。二维数组则通过行和列的下标访问元素,同样支持初始化和动态输入输出。此外,还简要介绍了C99标准中的变长数组,允许在运行时根据变量创建数组,但不能初始化。
34 6
|
23天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
22天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
26天前
|
存储 C语言
C语言:一维数组的不初始化、部分初始化、完全初始化的不同点
C语言中一维数组的初始化有三种情况:不初始化时,数组元素的值是随机的;部分初始化时,未指定的元素会被自动赋值为0;完全初始化时,所有元素都被赋予了初始值。
|
15天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
15 0