变量
变量的作用域:
(1).块变量 :语句块中定义的变量,只能在定义的语句块中使用
作用域范围: 从定义开始到}结束(语句块)
if 、for 、while 、do while {} ...
{
//块变量
}
(2). 局部变量 :在函数里面语句块外定义的变量,形参也是局部变量
作用域范围:从定义开始到本函数结束(函数内部)
(3). 全局变量 :在全局里定义的变量,在函数外面定义的变量,在程序中都可以访问
作用域范围:整个函数
① 在同一作用域下面,不能定义和声明同名的变量
② 在不同的作用域下面,可以定义同名的变量
③ 在访问同名变量时遵循局部优先原则 ,局部变量会隐藏全局变量局部优先原则:在搜索变量时,先找块变量;如果没有块变量,则查找局部变量,如果没有局部变量,则查找全局变量;如果全局还没有,则直接报错“未定义”。
(块变量 -> 局部变量 -> 全局变量)
在同名的情况下,如果要访问全局变量:
(1).提供语句块,然后在语句块中注入全局变
{
extern type gloable_var; // extern 类型 变量; 访问到的将是全局变量
}
(2).提供一个函数
type getX(){
return gloable_var;
}
(3).提供一个全局的指针
type *p = &gloable_var;
C语言程序内存地址分布:
要学习变量的属性存储,就需要了解一下程序的内存地址分布情况。
一个程序(进程)有4G的虚拟内存地址空间
sizeof(指针) == 4
0-3G 用户空间
3G-4G 内核空间
从低地址到高地址: 用户空间(代码区(text) -> 数据段(data) -> BSS -> 堆区(heap) —> (堆栈缓冲区) —> 栈区(stack)) —> 内核空间
代码区:存储代码文本、字面值
不可修改,只要修改代码区的内容,段错误(核心已转储)
数据取:存储的是已经初始化的全局变量 和 已经初始化的静态变量
BSS : 存储的是未初始化的全局变量 和 未初始化的静态变量 ,程序启动时会对该区域进行自动全部清零(擦除)
堆区: 动态内存(手动申请 ,手动释放) 从小到大扩张使用
栈区: 存储普通的局部变量和块变量 (形参、函数调用时的开销) 从大到小使用
静态内存,不需要程序员自己管理
函数调用之后,函数的内存会回收,不能返回局部变量的地址
命令行环境列表:
内核空间:不能直接访问,只能通过系统调用访问
变量的存储修饰:
- auto :自动变量 声明的变量默认就是auto auto可以省略 (c++中的auto变成了类型自动推导)
- static :静态变量 存储在全局数据区
静态变量:如果没有初始化,自动初始化为0
初始化的静态变量存储在数据区
未初始化的静态变量存储在BSS区
普通局部变量 和 静态局部变量:
(1). 存储位置:
普通局部变量:存储在 栈区
静态局部变量:存储在 全局数据区
(2). 生命周期
普通局部变量 生命周期:只在函数调用期间
静态局部变量 生命周期:是整个程序运行期间
(3). 执行次数
普通局部变量:每调用一次函数都会定义一次
静态局部变量:只有第一次执行时才会定义 ,静态局部变量会保存上一次调用结束之后的结果
(4). 作用域是一样的
普通全局变量 和 静态全局变量:
(1). 存储位置一样
(2). 生命周期一样
(3). 作用域不一样
普通全局变量的作用域是整个项目的所有文件
可以用extern声明一个变量在其他的文件中已经定义
(注入在其他文件中已经定义好的全局变量)
静态全局变量的作用域是当前文件(只能在当前.c文件中才能访问)
static 的作用:
(1). 修饰局部变量: 相对于普通局部变量,它的存储位置发生变化,保存到全局数据区,生命周期为整个程序
(2). 修饰全局变量: 相对于普通全局变量,作用域发生变化,只能在当前文件中访问
(3). 修饰函数: 只能当前文件中调用该函数,通过#include头文件也能调用
-
register :寄存器变量(保存在寄存器上面)
申请把变量作用寄存器变量 提高访问效率 (1). 只是一种请求,可能被拒绝 (2). 声明为register变量时,不能取内存地址,不能取& (3). 一般来说寄存器变量只能是4字节(寄存器相当于一个地址)
-
volatile :易变变量
拒绝效率优化,每次读取volatile变量时,都会去内存中重新加载一遍,以确保是正确的结果 一般用于中断程序处理中 多线程中 表示该变量可能遭遇意想不到的改变,所以每次在读取该值时都必须重新加载
-
const :
const修饰的变量表示只读,不可以修改(可以通过其他方式修改) const修饰的局部变量保存在栈区,可以通过指针修改内存区域的值 const修饰的全局变量保存在代码区,如果直接修改编译报错,如果通过指针修改,会段错误
const修改局部变量:
const int b = 10;
int *p = &b;
*p = 111;
printf("%d",b);
const char *p; 常量指针
const修饰*p *p只读 p本身的值可以修改
char const *p; 常量指针
等同于 const char *p;
char * const p; 指针常量
const修饰p p只读 *p的内容可以修改
const char * const p; 常量指针常量
p 和 *p 都是只读
const的作用:
(1). 修饰参数,防止在函数中意外修改实参的值
(2). 增强代码的健壮性
(3). 定义常量,增加代码可读性
const double PI = 3.1415926
-
extern:
(1).局部变量和全局变量同名时,在语句块中注入全局变量 访问到的是被局部同名变量隐藏掉的全局变量 (2).声明变量在其它文件中定义过(声明外部变量 或者 函数)的变量和函数
动态内存:
存在堆区,如果需要使用,需要自己申请,然后使用完之后手动释放。
头文件:#include <stdlib.h>
void *malloc(size_t size);
// 申请动态内存 size为申请的字节数
返回值为 void * (万能指针)可以转换为任意指针
如果申请失败 返回 NULL
需要对申请的动态内存进行成功的判断
void free(void *ptr);
// 释放动态内存
一定要确保参数ptr为malloc/calloc/realloc返回的值
如果ptr前面的控制块被修改,将会导致核心段错误
即使释放掉所有的动态内存,第一次申请的33页内存将一直会保留
注意:
(1)动态内存如果没有被释放将造成内存泄漏(这个问题很严重)
(2)动态内存不能被重复多次释放
(3)malloc虽然是申请size个字节的动态内存,但实际上却不是这个数字
第一次malloc分配了至少33页的内存空间,1页等于4096(4kb=4*1024byte)个字节
(4)在该内存没有全部使用之前,不会再申请更多的内容(只是在之前申请的内存中划一部分出来)
(5)申请多少个字节的内存,严格控制使用多少个字节的内存,不要越界
动态内存:堆内存 手动申请 手动释放
内存泄漏:申请的动态内存没有释放 或者 没有及时释放
内存碎片:多次申请和释放内存,会造成一些动态内存块中内存无法使用
需要使用大片的内存时用动态内存, 少部分的内存直接用栈内存
void *calloc(size_t nmemb, size_t size);
申请nmemb个size字节大小的连续内存 总共有nmembsize个字节
等同于malloc(nmembsize)
malloc申请的动态内存不一定会擦除内存中的数据,calloc一定会擦除
返回NULL表示失败
void *realloc(void *ptr, size_t size);
ptr:必须是指向动态内存
size:想要最终调整为size个字节大小的动态内存
调整动态内存大小为size个字节
注意:
(1) 调整动态内存之后必须重新接收该函数的返回值,调整大小之后,动态内存的位置可能发生改变(返回值和ptr可能不一样)
(2) 如果在扩大的时候,后面的动态内存已经被使用,那么将重新申请一块size个字节的动态内存,然后把之前的内存中的数据拷贝过来,释放之前的内存