首页 > 其他分享 >高级C语言1

高级C语言1

时间:2024-05-05 19:12:14浏览次数:23  
标签:变量 int 局部变量 高级 C语言 num 内存 全局变量

一、程序的内存分段:(进程映像)

​ 当执行程序的运行命令后,操作系统会给程序分配它所需要的内存,并划分成以下内存段供程序使用:

text 代码段:

​ C代码被翻译成二进制指令后存储在可执行文件中,当可执行文件被操作系统执行时,它会把里面的二进制指令(编译后的代码)加载到这个内存段,它里面的内容决定了程序如何执行,为了避免程序被破坏、修改,所以它的权限是只读。

​ 该内存段分为两个部分:

​ r-x:二进制指令 r--:常量数据

​ 注意:该内存段的内容如果被强制修改会产生段错误(非法使用内存)。

data 数据段:

​ 存储的是初始化过(初始化的值非零)的全局变量

​ 存储在该内存段的变量,被const修饰后,就会改存储到text内存段,变成真正的常量。

bss 静态数据段:

​ 存储的是未初始化的全局变量

​ 操作系统把程序被加载到内存后,会把该内存段进行初始化,也就是所有字节赋值为零,所以全局变量的默认值不是随机,而是零。

heap 堆:

​ 该内存段由程序员手动调用内存管理函数(malloc/free),进行分配、释放,它的分配释放受程序员的控制,适合存储一些需要长期使用的数据。

​ 它的大小不受限制,理论上能达到物理的上限,所以适合存储大量的数据。

​ 该内存段无法取名字,也就是无法与标识符建立联系,必须与指针配合使用。

stack 栈:

​ 存储的是局部变量、块变量

​ 该内存段会随着程序的执行自动的分配(定义局部变量、块变量)、释放(函数执行完毕自动释放局部变量、块变量),虽然使用比较方便,但它的释放不受程序员控制,长期使用的数据不能存储在栈内存中。

​ 该内存的大小有限,在终端执行: ulimit -s 可以查看当前系统栈内存的使用上限,我们使用虚拟机ubuntu的栈内存使用上限是8192kb,一旦超过这个限制就会产生段错误。可以使用ulimit -s 命令设置栈内存的使用上限。

静态内存:

​ 当程序完成编译 text、data、bss 三个内存段的大小就确定,在程序运行期间大小不会有任何变化,可以使用size命令查看程序的这三个内存段的大小。

sunll@:~/标准C语言$ size ./a.out
   text	   data	    bss	    dec	    hex	filename
   3884	    312	     96	   4292	   10c4	./a.out

动态内存:

​ heap、stack两个内存段,会随着程序的执行,而动态变化。

​ 当程序运行时,/proc/程序编号/maps 文件里记录程序执行过程中内存的使用情况,程序运行结束这个文件就消失了。

​ 使用ps aux 命令查看所有进程的编号,getpid函数可以获取当前进程的编号。

二、变量属性和分类

变量的属性

  • 作用域:变量的使用范围。
  • 存储位置:变量使用那个内存段存储数据,决定了变量在运行期间能否被释放(销毁),能否被修改。
  • 生命周期:变量从定义、分配内存到内存销毁的时间段。

全局变量:

​ 定义在函数外的变量叫全局变量。

  • 作用域:本程序内任何位置都可以使用。

  • 存储位置:初始化的全局变量使用的是data内存段,未初始化的全局变量使用的是bss内存段。

  • 生命周期:从程序开始执行,到程序执行结束。

局部变量:

​ 定义在函数内的变量叫局部变量。

  • 作用域:只能在它所在的函数内使用(从定义的位置开始,到函数结束)。

  • 存储位置:使用的是stack内存段。

  • 生命周期:当它所在的函数被调用后,执行到局部变量的定义语句时局部变量就会被创建(操作系统会给局部变量的变量名分配一块stack内存),当函数执行结束后,局部变量就被销毁了。

块变量:

​ 定义在if、for、while、do while语句块内的变量叫局部变量,就是特殊的局部变量。

  • 作用域:只能在它所在的语句块内使用。
  • 存储位置:使用的是stack内存段。
  • 生命周期:当它所在的函数被调用后,执行到块变量的定义语句时块变量就会被创建(操作系统会给块变量的变量名分配一块stack内存),当出了它所在的大括号,块变量就被销毁了。
int main() {
	for (int i = 0; i < 10; ++i) {   
        printf("%p\n",&i);
    }   
    //printf("%d\n",i);		//	 i已经被销毁,无法使用
    for (int j = 0; j < 10; ++j) {   
        printf("%p\n",&j);
    }
}
	//	i j 的地址编号相同的,但是循环变量i离开了for循环后已经被销毁了,j的地址相同只是刚好重新使用同一个内存而已
#include <stdio.h>
int num = 123;
int main() {
    printf("%d\n",num); 
    int num = 456;
    printf("%d\n",num);
    for (int i = 0;i < 1; ++i) {   
        printf("%d\n",num);
        int num = 789;
        printf("%d\n",num);
    }   
    printf("%d\n",num);                                                
}

注意:全局变量、局部变量、块变量可以同名,不会造成命名冲突,局部变量会屏蔽同名的全局变量,块变量会屏蔽同名的全局变量、局部变量。

解决: 一般为了解决全局变量与局部变量命名冲突问题,全局变量一般首字母大写,局部变量一般全部小写

全局变量的优点和缺点:

优点:

​ 使用方便,避免了函数之间传参产生的消耗,提高程序的运行速度。

缺点:

​ 程序运行期间全局变量所占用的内存不会被销毁,可能会产生内存浪费。

​ 命名冲突的可能性比较大,可能会与其它文件的全局变量、函数、结构、联合、枚举、宏命名冲突。

#include <stdio.h>

int scanf;	//	全局变量,很容易起命名冲突
int main() {
    int scanf;	//	局部变量 不容易起冲突
}
总结:

​ 全局变量尽量少用,或者不用。

三、修饰变量的关键字——类型限定符

<类型限定符> 数据类型 变量名;

typedef

typedef int num;
num n1;	//n1 就是int类型

​ 变量名被typedef修饰后,就会变成定义它的数据类型,此时该名字不是变量名而是类型名,之后就可以使用这种新的数据类型定义变量、数组了,该功能是为了给复杂的数据类型重新定义一个简短的类型名。

​ 由于无符号整型使用比较麻烦,所以标准库中为我们定义一些简短的无符号整型的类型名,就使用typedef定义的,实现在stdint.h头文件里。

typedef signed char     int8_t;
typedef short int       int16_t;
typedef int         	int32_t;
typedef long long int   int64_t;

typedef unsigned char       uint8_t;
typedef unsigned short int  uint16_t;
typedef unsigned int        uint32_t;
typedef unsigned long long int  uint64_t;

注意:在之后的学习过程中,如果遇到一些xxx_t的数据类型,都使用typedef重定义,例如:time_t,size_t pid_t。

注意:后续在定义结构时,可以使用typedef缩短结构类型名

auto

auto int num;

​ 早期的C语言用它来修饰自动分配、释放内存的变量,也就是局部变量和块变量,但由于代码使用的变量绝大多数都是局部变量和块变量,所以就约定,该关键字不加就代码加,所以该关键字已经没有实用价值了。

​ 在C++11的语法标准中,auto有了新的功能,就是定义自动类型的变量,编译器会根据变量的初始值,自动设置变量的数据类型。
​ auto num = 1234; // num int类型

​ auto f = 3.14; // f double类型

​ 编译指令:g++ xxx.c -std=c++11

​ 注意:虽然auto关键字,已经不再使用,但基本功能还保留着,所以它不能修饰全局变量。

const

const int num;

​ const的意思是常量,但实际它只是为变量提供一层保护,被它修饰的变量不能显式修改,但可以隐式修改,也就被它修饰后并不能变成真正的常量。

#include <stdio.h>
int main() {
    const int num = 10; 
    int* p = (int*)&num;                                               
    *p = 88; 
    //num = 88;
    printf("%d\n",num);
    printf("%d\n",num);
}

​ 注意:存储在data内存段的变量,被const修饰后就会变成真正的常量,存储位置被修改为text,其实是修改了data段和text段的分界线。如果就算隐式修改也会段错误

static

​ static既可以修饰变量,也可以修饰函数,主要有三大功能:

限制作用域:

​ 默认情况下全局变量、函数的作用域是整个程序都可以使用,被static修饰后,就只能在它所在的.c文件内使用。

​ 该功能可以避免全局变量、函数的命令冲突,也能防止全局变量、函数被外部修改、调用,提高代码的安全性。

​ 普通全局变量、函数也叫外部变量、外部函数,被static修饰后就叫做内部变量、内部函数、静态全局变量。

改存储位置:

​ 局部变量、块变量被static修饰后,存储位置就由stack改data、bss,称呼为静态局部变量、静态块变量。

​ 静态局部变量、静态块变量的默认值不再是随机的,而是零。

延长生命周期:

​ 由于静态局部变量、静态块变量的存储位置由stack(动态分配、释放)改为data、bss,所以静态局部变量、静态块变量不会随着函数的执行结束而销毁,而是和全局变量的生成周期一样。

注意:

​ static修饰局部变量、块变量,会改变它们的存储、延长生命周期,但并不会改变它们的作用域。

volatile

int num1 = 10;
printf("%d\n",num1);
num1+10;
num1*100;
volatile int num;	//	告诉编译器不要做取值优化

​ 在程序中使用到num变量时,系统会从内存中读取该num的值交给CPU运算,如果之后num变量的值没有发生明显变化,再次使用变量时系统会直接使用上次读取的旧值,而不会再从内存中读取。这编译器对变量读值过程的优化。

​ volatile 关键字就告诉编译器不要优化变量的读值过程,每使用该变量时,都重新从内存中读取它的值。

int num = 10;
if(num == num) {
    //	一定成立
}

volatile int num = 20;
if(num == num) {
    //	有可能不成立
}

​ 什么情况下需要使用volatile关键字:

​ 变量被共享访问,且有多个执行者可以修改它的值,这种情况下变量就应该被volatile修饰。

​ 情况1:多线程编程处理复杂问题时。

​ 情况2:裸机编程、驱动编程时,软硬件共用的寄存器。

register

​ 计算机的存储介质读写速度排序:机械硬盘->固态硬盘->内存条->高级缓存->CPU寄存器

​ register关键字的作用是申请把变量的存储介质由内存条改为CPU寄存器,一旦申请成功,变量的读写速度、运算速度会大大提高。

注意:CPU中的寄存器数量有限,申请不一定成功,只有需要长期大量运算的变量才适合用register关键字修饰。

注意:被register修饰过的变量,不能获取变量的地址。

extern

​ 当使用其它.c文件中的全局变量时,需要像声明函数一样,对其它.c文件全局变量进行声明。

extern 类型 变量名; 

注意:声明变量只能解决编译时的问题,如果目标文件最终链接时,变量没有定义,依然会报错。

a.c:(.text+0x12):对‘num’未定义的引用,这种是链接时的错误。

标签:变量,int,局部变量,高级,C语言,num,内存,全局变量
From: https://www.cnblogs.com/sleeeeeping/p/18173745

相关文章

  • 06. C语言指针
    【指针】C语言使用数据名调用数据,数据名相当于C语言的直接寻址,直接寻址只能调用固定数据,而指针是间接寻址,指针存储了另一个数据的地址,使用指针调用数据时首先取指针存储的内存地址,之后使用此地址调用数据,使用间接寻址有如下几点优势:1.统一数据的调用方式,因为指针是调用数据的中间......
  • 标准C语言5
    进制转换:​ 现在的CPU只能识别高低两种电流,只能对二进制数据进行计算。​ 二进制数据虽然可以直接CPU计算识别,但不方便书写、记录,把二进制数据转换成八进制是为了方便记录在文档中。​随着CPU的不断发展位数不断增加,由早期的8位逐渐发展成现在的64位,因此八进制就不能满......
  • 标准C语言4
    一、函数什么是函数:function​函数就是一段具有某一项功能的代码集合,它是C语言中管理代码的最小单位,把具有某项功能的若干行代码封装在函数中方便管理代码且方便重复调用。函数的分类:标准库函数:​ C语言标准委员会为C语言以函数形式提供了一些基础功能,这些函数被封装在li......
  • 标准C语言3
    一、数组什么是数组:​ 数组就是变量的组合,是一种批量定义变量的方式如何定义数组:类型名数组名[数量]; intarr[8];// 相当于定义了8个int类型的变量 inta1,a2,a3,...;访问数组中的变量:数组名[下标]; 下标从0开始,范围0~数量-1遍历数组:与for循环配合,使用循环变量作......
  • 对C语言符号的一些冷门知识运用的剖析和总结
    符号目录符号注释奇怪的注释C风格的注释无法嵌套一些特殊的注释注释的规则建议反斜杠'\'反斜杠有续行的作用,但要注意续行后不能添加空格回车也能起到换行的作用,那续行符的意义在哪?反斜杠的转义功能单引号和双引号字面值,字符串,字符,字符变量的大小为什么sizeof('1')的大小是4?c......
  • 标准C语言2
    二、常量(了解)​ 常量就是程序运行过程中不能改变的量,C语言中常量有:字面值常量、宏常量、枚举常量。字面值常量100 int100l long100ll longlong100u unsignedint100lu unsignedlong100llu unsignedlonglong定义一个宏常量表示100年总共有多少秒,不考虑闰平年 #defin......
  • C语言 子进程段错误后变成僵尸进程
    空指针获取首元素时出现段错误,子进程异常退出,父进程没有处理。#include<stdio.h>#include<unistd.h>intmain(){pid_tpid;pid=fork();if(pid>0){printf("fatherprocessisPID:%d\n",getpid());while(1){......
  • 标准C语言1
    一、C语言介绍​ 丹尼斯.里奇和肯.汤普逊在1971~1973年美国贝尔实验室,在开发UNIX操作系统时,在BCPL语言的基础上(newB语言),发明第一款高级编程语言,取BCPL第二个字母作为名字,所以叫C语言​ BCPL->newB->C->UNIX->Minix->Linux​ 它是为了开发操作系统而研发的一款编程语言,它特......
  • C语言 父子进程不能共享全局变量
    父子进程不能共享全局变量。父子进程中的任何一方修改了全局变量,只是修改了副本,只对自己可见,对另一方不可见。C语言中即使加了static也不行。#include<stdio.h>#include<unistd.h>//初始值是0intflag;intmain(){pid_tpid;//父进程和子进程执行相同代码即......
  • C语言转写成MIPS指令集汇编以及MIPS指令集汇编中函数调用时栈的变化
    一、问候语欢迎你来到我的博客!二、C语言代码分析  这段C语言代码共有3个函数组成。set_array函数传入1个int类型的变量num,并创建了1个int类型临时变量i和1个临时int类型数组array,里面含有10个单位,此函数主要目的是调用compare函数,并将num和i传入该函数中,得到其函数返回值......