C语言深度解剖
- 格式:pdf
- 大小:384.47 KB
- 文档页数:60
C语言中内存四区的详解C语言编程2022-05-10 14:00来自:今日头条,作者:抖点料er链接:https:///article/7046019680989037069/1、内存四区1.1数据类型本质分析1.1.1数据类型的概念•“类型”是对数据的抽象•类型相同的数据有相同的表示形式、存储格式以及相关的操作•程序中使用的所有数据都必定属于某一种数据类型1.1.2数据类型的本质•数据类型可理解为创建变量的模具:是固定内存大小的别名。
•数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
•注意:数据类型只是模具,编译器并没有分酤空间,只有根据类型(模具)创建变量(实物),编译器才会分配空间。
1.2变量的本质分析1.2.1变量的概念概念:既能读又能写的内存对象,称为变量;若一旦初始化后不能修改的对象则称为常量。
变量定义形式:类型标识符,标识符,…,标识符;1.2.2变量的本质1、程序通过变量来申请和命名内存空间int a = 0。
2、通过变量名访问内存空间。
1.3程序的内存四区模型流程说明1、操作系统把物理硬盘代码load到内存2、操作系统把c代码分成四个区栈区( stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等堆区(heap):一般由程序员分配释放(动态内存申请与释放),若程序员不释放程序结束时可能由操作系统回收全局区(静态区)(statIc):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放常量区:字符串常量和其他常量的存储位置,程序结束后由操作系统释放。
程序代码区:存放函数体的二进制代码。
3、操作系统找到main函数入口执行1.4函数调用模型1.5函数调用变量传递分析(1)(2)(3)(4)(5)1.5栈的生长方向和内存存放方向相关代码:02_数据类型本质.c#define _CRT_SECURE_NO_WARNINGS #include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int main(){int a;//告诉编译器,分配4个字节int b[10];//告诉编译器,分配4*10个字节/*类型本质:固定内存块大小别名可以通过sizeof()测试*/printf("sizeof(a)=%d,sizeof(b)=%d\n", sizeof(a), sizeof(b));//打印地址//数组名称,数组首元素地址,数组首地址printf("b:%d,&b:%d\n",b,&b);//地址相同//b,&b数组类型不同//b,数组首地址元素一个元素4字节,+1 地址+4//&b,整个数组首地址一个数组4*10=40字节, +1 地址+40 printf("b+1:%d,&b+1:%d\n", b + 1, &b + 1);//不同//指针类型长度,32位机器32位系统下长度是 4字节// 64 64 8char********* p = NULL;int* q = NULL;printf("%d,%d\n", sizeof(p), sizeof(q));//4 , 4return0;}03_给类型起别名.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>typedef unsigned int u32;//typedef 和结构体结合使用struct Mystruct{int a;int b;};typedef struct Mystruct2{int a;int b;}TMP;/*void 无类型1.函数参数为空,定义函数时用void修饰 int fun(void)2.函数没有返回值:使用void void fun (void)3.不能定义void类型的普通变量:void a;//err 无法确定是什么类型4.可以定义 void* 变量 void* p;//ok 32位系统下永远是4字节5.数据类型本质:固定内存块大小别名6.void *p万能指针,函数返回值,函数参数*/int main(){u32 t;//unsigned int//定义结构体变量,一定要加上struct 关键字struct Mystruct m1;//Mystruct m2;//errTMP m3;//typedef配合结构体使用struct Mystruct2m4;printf("\n");return0;}04_变量的赋值.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int main(){//变量本质:一段连续内存空间别名int a;int* p;//直接赋值a = 10;printf("a=%d\n", a);//间接赋值printf("&a:%d\n", &a);p = &a;printf("p=%d\n", p);*p = 22;printf("*p=%d,a=%d\n", *p, a);return0;}05_全局区分析.c#define _CRT_SECURE_NO_WARNINGS #include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int main(){//变量本质:一段连续内存空间别名int a;int* p;//直接赋值a = 10;printf("a=%d\n", a);//间接赋值printf("&a:%d\n", &a);p = &a;printf("p=%d\n", p);*p = 22;printf("*p=%d,a=%d\n", *p, a);return0;}06_堆栈区分析.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>char* get_str(){char str[] = "abcdef";//内容分配在栈区,函数运行完毕后内存释放printf("%s\n", str);return str;}char* get_str2(){char* temp = (char*)malloc(100);if (temp == NULL){return NULL;}strcpy(temp, "abcdefg");return temp;}int main(){char buf[128] = { 0 };//strcpy(buf,get_str());//printf("buf = %s\n", buf);//乱码,不确定内容char* p = NULL;p = get_str2();if (p != NULL){printf("p=%s\n", p);free(p);p = NULL;}return0;}07_静态局部变量.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int* getA(){static int a = 10;//在静态区,静态区在全局区return &a;}int main(){int* p = getA();*p = 5;printf("%d\n",);return0;}08_栈的生长方向.c#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>int* getA(){static int a = 10;//在静态区,静态区在全局区return &a;}int main(){int* p = getA();*p = 5;printf("%d\n",);return0;}版权申明:内容来源网络,版权归原创者所有。
C语言深度解剖第一章:关键字1.1 定义声明Int i;定义创建了对象并为这个对象分配了内存Extern int i;声明没有分配内存。
1.2 auto在缺省的时候,编译器默认所以的变量都是auto的。
自动变量1.3 register 声明寄存器变量Register:这个关键字请求编译器尽可能地将变量存在CPU内部寄存器中,而不是通过内存寻址访问以提高效率。
(cpu:皇帝,寄存器:太监,内存:大臣,不考虑CPU的高速缓存区cache)寄存器的存取速度比内存块得多。
但在使用register修饰符也有些限制的:register变量必须是能被CPU寄存器所接受的类型。
这就意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度,而且register变量可能不存放在内存中,所以不能用取地址运算符“&”来获取register变量的地址。
1.4 static关键字2个作用:修饰变量、修饰函数。
第一个作用:修饰变量:局部变量和全局变量,存放于内存的静态区。
静态全局变量:作用域仅限于变量被定义的文件中,其他文件即使使用extern声明也没法使用它;要想在定义之前使用,就必须在前面加extern ***(故static声明的全局变量最好在文件顶端定义)。
静态局部变量:在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其它函数也用不了。
由于被static修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值也不会被销毁,函数下次使用时任然能用到这个值。
例:#include<stdio.h>static int j;int k = 0;void fun1(void){static int i;i++;}void fun2(void){j++;}int main(void){for(k = 0;k<10;k++){fun1();fun2();}return 0;}解析:i、j看起来没有初始化,其实默认值为0.程序结束后i=10,j=10.第二个作用:修饰函数:成为静态函数,对函数的作用域仅限于本文件(所以又称内部函数)。
c语言深度剖析
C语言是一门广泛应用于计算机编程领域的高级编程语言,它的设计目标是提供一种能够以简洁的方式编写系统软件的语言。
C语言的出现极大地推动了计算机科学的发展,成为了现代计算机编程的基础。
在深度剖析C语言时,我们需要从以下几个方面进行分析:
1. 语言特性
C语言的语言特性包括数据类型、运算符、控制语句、函数等。
其中,数据类型是C语言的基础,包括整型、浮点型、字符型等。
运算符包括算术运算符、关系运算符、逻辑运算符等。
控制语句包括条件语句、循环语句等。
函数是C语言的重要特性,它可以将程序分解为多个模块,提高程序的可读性和可维护性。
2. 内存管理
C语言的内存管理是其最为重要的特性之一。
C语言中的内存分为栈、堆、全局变量等。
程序员需要手动管理内存,包括内存的分配和释放。
如果内存管理不当,会导致内存泄漏、内存溢出等问题,严重影响程序的稳定性和性能。
3. 指针
指针是C语言的重要特性之一,它可以让程序员直接访问内存中的
数据。
指针可以用于数组、函数、结构体等。
指针的使用需要注意指针的类型、指针的初始化、指针的运算等问题。
4. 预处理器
C语言的预处理器是其独特的特性之一,它可以在编译之前对源代码进行处理。
预处理器可以用于宏定义、条件编译等。
预处理器的使用需要注意宏定义的作用域、宏定义的参数等问题。
深度剖析C语言需要对其语言特性、内存管理、指针、预处理器等方面进行全面的分析和理解。
只有深入了解C语言的特性和使用方法,才能更好地应用C语言进行编程,提高程序的质量和效率。
C语言深度解剖读书笔记之——C语言基础测试题前几天天看到这本书,感觉不错,在看之前,先做了后面的习题,结果只得了60多分,一直以为自己的基础还是不错的,做完后对了答案后,感觉自己的自信心一下全没有了,不过遇到问题解决问题,我用了2天时间好好研读了这本书,感觉真不错。
虽然感觉都是一些基础的知识,但是我读的还是津津有味,感觉收获蛮多的,感谢这本书的作者陈正冲。
呵呵,说来我本科专业和这位大牛还是同一个专业呢,呵呵。
不是只有计算机科班出身的才能学好编程,知真正的高手都是自学的。
今天就把我当时做错的题目和认为比较好的题目一个个写出来。
再次分析下如果我在哪家公司遇到类似这种题目我会感觉这家公司出题很有水平,重基础,真正理解C 语言的人才能得高分。
注重细节,知其然知其所以然。
题目1.下面代码有什么问题,为什么?[cpp]view plaincopyprint?1.#include <iostream>ing namespace std;3.4.int _tmain(int argc, _TCHAR* argv[])5.{6.char string[10],str1[10];7.int i;8.for (i=0;i<10;i++)9.{10.str1[i] = 'a';11.}12.strcpy(string,str1);13.cout<<string<<endl;14.system("pause");15.return 0;16.}运行的时候回出现下面情况:error1.exe 中的0xcccc6161 处未处理的异常: 0xC0000005: Access violation做这个题目的时候,我也知道字符串strcpy是以'\0'为结束标识的。
会产生数组越界,但是表达不清楚。
答案:运行到strcpy的时候可能会产生内存异常。
关键字Auto:声明自动变量。
在缺省情况下,编译器默认所有变量都是auto的定义和声明:int I;定义就是创建一个对象,为这个对象分配一块内存并给他取一个名字。
一个变量或对象只能定义一次Extern int i声明:告诉编译器,这个名字已经匹配到一块内存上,这个名字已经被预定,别的地方不能再用他当变量名或对象名。
声明可以出现多次Extern可以置于变量或函数前,以表明变量或函数的定义在别的文件中Register:请求编译器尽可能地将变量存在CPU内部寄存器中,而不是通过内存寻址访问注意点,register变量必须是能被CPU寄存器所接受的类型,这就是说register变量必须是单值,并且其长度小于或等于整型的长度。
而且变量不能用取地址符&Static作用:1修饰退出一个块后仍然存在的局部变量2表示不能被其他文件访问的全局变量和函数静态全局变量,作用域仅限于变量被定义的文件中,其他文件及时使用extern声明也没法使用它。
静态局部变量,在函数体里面定义的,就只能在这个函数里使用,同一个文档中的其他函数也用不了。
由于被static修饰的变量总是存在内存的静态区。
当这个函数运行结束,静态变量也不会销毁修饰函数:指对函数的作用域限于本文件,使用静态函数的好处是,不同的人编写不同的函数时,不用担心自己定义的函数是否会有其他文件中的函数同名Bool变量的初始化和if条件句的规范Bool bTestFlag=FALSE;If(bTestFlag); if(!bTestFlag);float变量的初始化和if条件句的规范float fTestVal=0.0;if((fTestVal>=-EPSINON)&&(fTestVal<=EPSINON));指针变量Int *p=NULL;If(NULL==p); if(NULL!=p);Case后面只能是整型或字符型的常量或常量表达式按字母顺序排列各条case语句按执行频率排列case语句Break表示终止本层循环在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的放在最外层,以减少CPU跨切循环层的次数如果函数的参数可以是任意类型指针,那么应声明其参数为void*如内存操作函数void* memcpy(void* dest,const void* src,size_t len)Return语句不能反悔指向栈内存的指针,因为该内存在函数体结束时被自动销毁如char *Func(void){Char str[30];…..Return str;}Const修饰的只读变量先忽略类型名,看const离哪个近就修饰谁Const int *p 修饰*p,*p是指针指向的对象,不可变int * const p 修饰p,p不可变const修饰函数的返回值,返回值不可被改变const int Fun (void)volatile关键字修饰的变量,提醒编译器对访问该变量的代码不再进行优化int i=10;in j=I; //1语句int k=I; //2语句执行上面代码时编译器对代码优化,在1语句时从内存中取出i的值赋给j之后,这个值并没有丢掉,而是在2语句时继续用这个值给k赋值Volatile int i=10;Int j=I;Int k=I;执行上面代码,volatile关键字告诉编译器,i是随时可能发生变化的,每次使用他的时候必须从内存中取出i的值Union中所有的数据成员公用一个空间,所有的数据成员具有相同的起始地址大端模式:字数据的高字节存储在低地址中,低字节存放在高地址中小端模式:字数据的高字节存储在高地址中,低字节存放在低地址中Typedef:给一个已经存在的数据类型(注意:是类型,不是变量)其一个别名Typedef struct student{//code}Stu_st,*Stu_pststruct student *stu1、Stu_pst stu、Stu_st *stu——没有区别表示给struct student{//code}取了个别名叫Stu_st,同时给struct student{//code}*取了个别名叫Stu_pstConst Stu_pst stu2这里Stu_pst是struct student{//code}*的别名。
《C 语言深度解剖》总结(陈正冲编著)1.register关键字请求编译器尽可能的将变量存储在CPU内部寄存器中而不是内存中以提高访问效率。
数据从内存中取出来首先放到寄存器中,然后CPU再从寄存器中读取数据来进行处理,处理完后同样通过寄存器放到内存中,CPU不直接和内存打交道。
Register的值必须是一个单个的值,并且其长度应小于或等于int的长度,而且register 变量可能不放在内存中,所以不能用取地址符(&)来获取register变量的地址。
2. #include <cstdlib>#include <iostream>using namespace std;int main(int argc, char *argv[]){char a[1000];for(int i=0; i<1000; i++)a[i] = -1 - i;cout << strlen(a) << endl;system("PAUSE");return EXIT_SUCCESS;}输出结果为:255先来了解下补码的知识:在计算机中,数值一律用补码来表示(存储),主要原因是使用补码,可以将符号位和其它位统一处理,同时,减法也可以按加法来处理,两外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍去。
整数的补码和其原码一致,负数的补码:符号位为1,其余位为该数绝对值的原码按位取反,然后整个数加1.于是,for()循环中,当i = 255时,a[255] = -256, 由于char类型的只能表示-128~127之间的数,所以-256已经发生了溢出,只将最低8位赋给a[255], -256的补码的低8位为0,所以a[255] = 0, 也就是字符串的结尾符’\0’的ascii值,当用strlen()计算a的长度时,遇到a[255]时停止,于是strlen(a) = 255; (a[0]~a[254])3. 下面输出:#include <iostream>#include <string>#include <bitset>using namespace std;int main(){//freopen("output.txt", "wt", stdout);int i = -20;unsigned j = 10;bitset<32> b(i + j);int res = i + j;cout << b << endl;cout << hex << i + j << endl;cout << dec << res << endl;system("PAUSE");return 0;}输出结果:11111111111111111111111111110110fffffff6-10分析:当表达式中既有signed和unsigned时,计算结果为unsigned类型的,所以i + j的结果也是unsigned类型的,i + j = -10, -10的补码可以按照上面说的计算补码的介绍得出…如果把i + j的结果复制给int类型的,则-10的补码会被解析成有符号类型的,故res 的输出为-10;4.#include <iostream>#include <string>using namespace std;int main(){unsigned i;for(i=9; i>=0; i--)cout << "#####" << endl;system("PAUSE");return 0;}程序将无限的输出####, 因为i为unsigned类型的,永远>=0, 所以上面的for()是个死循环…5.如果函数没有返回值,那么声明为void 类型。
第一章关键字static1 修饰变量若在函数体内定义,则只适用本函数;若在函数体外定义,则从定义位置往后函数有效,若定义前函数向调用该变量,也得采用extern 声明。
备注:无论如何,static 声明的变量只能使用本文件中,其他文件即使用extern声明不但无法适用,编译器会报错。
2 修饰函数修饰函数时候,不是表达内存关系,而是声明该函数仅作用于本文件,其他文件调用会报错。
sizeof注意:1 sizeof是个关键字,而并非一个函数2 sizeof在测试变量长度时候其括号可以省略,但是测试数据类型(莫子)时候,则,不能省略。
例如:int i ; sizeof(i)==sizeof i ;但是sizeof(int) ; 合法,sizeof int ; 非法void1 定义:void valuble ;会报错void *valuble_pc;正确;2 空类型指针可以包容任何类型指针,但是任何类型指针却不可包容空类型指针。
void *valuble_pc;int *int_pc;valuble_pc = int_pc;//合法int_pc = valuble_pc;//非法3 void 最重要的应用是在函数返回值以及形参上。
一点需要明确:任何函数默认下的返回值都为int 类型。
return1 return 终结函数并可以返回相应类型值。
备注:return 返回函数指针时候,不要返回本函数局部变量地址(why?;自己想想)const1 const修饰的变量应该是一个只读变量,但并不意味着该变量就是常量:const int MAX_NO = 100;int recive[MAX_NO]; //编译器报错(Why?)2 const修饰变量可以放在变量类型之前,也可以放在变量类型之后const int i ;= = int const i ;3 修饰指针类型变量int dat1 = 9,dat2 =98;const int *pc1 = &dat1; //pc(地址可变);*pc(指针指向对象不可变)int const *pc2; //pc(地址可变);*pc(指针指向对象不可变)int * const pc3; //pc(地址不可变);*pc(指针指向对象可变)const int * const pc4; //pc(地址不可变);*pc(指针指向对象不可变)pc1 = &dat2 ; //合法*pc1 = dat2; //非法(Why?)(其他同理)4 修饰函数形参void fun(const int i){//code}表示:形参中,i的值不能改变;volatile1 个人理解:从改变编译器优化角度,因为编译器对于一般变量的读取都是编译器明确(很有把握该变量没有逃出编译器可见范围内更改值),这样,编译器在读取时候,如果编译器确定该变量之前读取到现今操作仍未改变,则编译器不再浪费时间的从先从变量地址获取该变量值,这些本来都是编译器优化的思想。
嵌入式C语言的深入了解本文并不一本详细C语言教材,只是重点分析与嵌入式相关的各种C语言要求,这些要求构成了一般C程序与嵌入式C程序开发者之间重大区别.本课程是作为嵌入式开发的前导课程来准备C语言类型修饰符.C语言支持可以见到6种类型修饰符,分别是: auto, const, register, static, volatile, extern.? 局部变量除非显式指明为static, 否则默认为auto,所以一般不会在代码中使用类型修饰符auto.? extern用于声明全局变量或全局函数,使用者要注意extern 的后的语句并不能产生实际定义.? Register是通知编译器,尽可能让所修饰的变量以寄存器的形式使用.在较新的编译器,优化器可以合理的分配寄存器,所以一般不会在代码中使用类型修饰符register.但是在单片机或嵌入式开发,有时强制使用register还是有实际含意.? 而const,static,volatile有一些特定的需求Staticstatic 用于声明局域变量或局域函数,一个具有模式化编程的程序员应该多使用static,将私有数据和私有函数用static保护起来,不被外界访问.这一关键字起的作用类似于C++的private .1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。
它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。
那就是,这个函数被限制在声明它的模块的本地范围内使用。
在一个大型项目里,static 被广泛使用,如果一个嵌入式C程度员很少使用static ,说明他至少对于数据保护的好处认识不够.ConstConst也是应该广泛的关键字,表示后面定义有可能是一个不变的常量,但是跟各种数据类型定义组合,能演变出复杂含义.Const 主要有如下应用情况1. 声明后面是一个常量2. 如果跟指针组合,将形成复杂的组合情况3. 作函数参数数的修饰符.表示不能修改这一参数的值.主要是指针4. 作函数返回值, 表示调用函数不能修改这一参数数的值.主要是指针Const 让人感到复杂是的,他的位置可以与被修饰的数据类型互换!其它的修饰符没有这样用法.这样换一般情况下是等效的,如 const int c1= 5; 等效于 int const c1=5;但是,我说是但是,如果数据类型是一个指针,互换一样位置表示完全不同含义,所以const 这一个用法经常是面试官用来折磨嵌入式C面试者的法宝之一.参看如下定义int b = 500;const int* a = &b; [1]int const *a = &b; [2]int* const a = &b; [3]const int* const a = &b; [4]这种定义要看 const 的位置来确定他的用法.const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
C语言深度解剖学习笔记《C语言深度解剖》前言:如果本书上面的问题能真正明白80%,作为一个应届毕业生,肯怕没有一家大公司会拒绝你。
第一章关键字什么是定义?什么是声明?什么是定义:所谓的定义就是(编译器)创建一个对象,为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名。
但注意,这个名字一旦和这块内存匹配起来,它们就同生共死,终生不离不弃。
并且这块内存的位置也不能被改变。
一个变量或对象在一定的区域内(比如函数内,全局等)只能被定义一次,如果定义多次,编译器会提示你重复定义同一个变量或对象。
什么是声明:有两重含义,如下:第一重含义:告诉编译器,这个名字已经匹配到一块内存上了,下面的代码用到变量或对象是在别的地方定义的。
声明可以出现多次。
第二重含义:告诉编译器,我这个名字我先预定了,别的地方再也不能用它来作为变量名或对象名。
定义声明最重要的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存C语言标准定义的32个关键字关键字意义auto声明自动变量,缺省时编译器一般默认为autoint声明整型变量double声明双精度变量long声明长整型变量char声明字符型变量float声明浮点型变量short声明短整型变量这六个关键字代表C语言里的六种基本数据类型在32位的系统上short内存大小是2个byte;int内存大小是4个byte;long内存大小是4个byte;float内存大小是4个byte;double内存大小是8个byte;char内存大小是1个byte。
(注意这里指一般情况,可能不同的平台还会有所不同,具体平台可以用sizeof关键字测试一下)signed声明有符号类型变量unsigned声明无符号类型变量正负数:最高位如果是1,表明这个数是负数。
如果最高位是0,表明这个数是正数struct声明结构体变量不要认为结构体内不能放函数\union声明联合数据类型union只配置一个足够大的空间以来容纳最大长度的数据成员enum声明枚举类型成员都是常量,也就是我们平时所说的枚举常量(常量一般用大写)。