告白C开发之C语言(一)
在大学期间也学习过C语言,感觉懵懵懂懂,各大编程培训班的视频也看过不少,但大部分一开始讲的都是语法,学完只知道是什么、怎么做,在项目中不遇到问题还好,一遇到问题就蒙圈了,很难查到原因,自己反思了下,是因为我们大多数人在C编程的时候只学习到了C的语法规则,并没有像编译器和CPU一样去思考导致的,当然我也是工作两年后才慢慢明白的,刚好自己有时间就想将自己的感悟记录下来,后期也会从头去整理自己学C的整个过程并发布到平台上来。
第一章:C语言中的内存
第一节:程序运行为什么需要内存?
1、为什么人要不断的去写程序,原来已经有的程序为什么不能直接用?
因为:写程序的目的就是为了得到不同的计算结果,原来的程序不一定会达到我们想要的结果;
因此也就推导出了什么是程序;
程序就是:代码+数据,代码执行相应的动作对数据进行加工,最后得到我们想要的结果。
2、程序运行的目的?
主要有两个不同类型的目的:结果、过程
第一个是:得到一个具体的结果(对应程序的函数要求有返回值)(比如说要进行几个数的排序,那么我就要有一个排序的结果)。 第二个是:要求有一个具体的过程(对应C语言中函数类型是 void function(void)的,即没有返回值的。)(比如说我要进行延时,那么我延时就是要运行一些没有意义的代码,不需要我有一个返回值,所以我要的就只是咬执行延时代码的这么一个过程)
3、什么是形参呢?
形参就是程序中待加工的数据,当然在函数中也还会有一些零时数据,这些数据就是局部变量,比如定义的int i=... j=....之类的。
eg:
int add(int a, int b)
{
return a+b;
}
这个函数的执行就是为了得到结果,返回结果就是函数的最终值a+b;
void add(int a, int b)
{
int c;
c=a+b;
printf("c=%d.\n",c);
}
这个函数的执行就注重过程(重在过程中的printf),是没有返回值的
int add(int a, int b)
{
int c;
c=a+b;
printf("c=%d.\n",c);
return c;
}
这个函数就又注重结果又注重过程
void的使用:
规则一
如果函数没有返回值,那么应声明为void类型
规则二
如果函数无参数,那么应声明其参数为void。
解释:void function(void)
其中第一个void表示没有返回值,第二个void表示没有传参。
规则三
小心使用void指针类型
按照ANSI(American National Standards Institute)标准,不能对void指针进行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
//ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。
//例如:
int *pint;
pint++; //ANSI:正确
pint++的结果是使其增大sizeof(int)。( 在VC6.0上测试是sizeof(int)的倍数)
但是大名鼎鼎的GNU(GNU's Not Unix的缩写)则不这么认定,它指定void *的算法操作与char *一致。
因此下列语句在GNU编译器中皆正确:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
pvoid++的执行结果是其增大了1。( 在VC6.0上测试是sizeof(int)的倍数)
在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void * pvoid;
(char *)pvoid++; //ANSI:正确;GNU:正确
(char *)pvoid += 1; //ANSI:错误;GNU:正确
GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合ANSI标准。
5.规则四
如果函数的参数可以是任意类型指针,那么应声明其参数为void *
典型的如内存操作函数memcpy和memset的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果memcpy和memset的参数类型不是void *,而是char *,那才叫真的奇怪了!这样的memcpy和memset明显不是一个“纯粹的,脱离低级趣味的”函数!
6.规则五
void不能代表一个真实的变量
下面代码都企图让void代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
void体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(还有人妖?)。
4、从实际中来体会冯诺依曼结构和哈佛结构。
在嵌入式中一般采用的是哈佛结构,即数据和程序分别存储(这个存储的地方一般叫硬盘),程序运行是CPU将程序从硬盘中读取到DRAM当中(这个DRAM就是所谓的内存)这时数据和代码都在内存中,所以这叫冯诺依曼结构;
在单片机中,程序是烧写到NORFlash中的(对应只读存储ROM),程序运行的时候就是在NORFlashe中打转,而程序锁涉及到的数据(全局变量,局部变量)都是放在flash中(SRAM这个东西的性质和RAM是同类的,但是SRAM不需要初始化,可以直接使用),这种就是哈佛结构。
5、静态内(SRAM)存和动态内存(DRAM)的区别。
①静态内存:静态内存呢里面存放的是我们定义的一些变量,这是在我们定义之后,由编译器自动的就分配好了的内存,使用完了呢,这部分内存就会释放出来,同时呢因为这个过程是编译器做好的,我们使用的时候就不会占用CPU的资源。
②动态内存:动态内存呢比较大(比静态内存大的多),用户呢无法确定其内存的大小,使用的时候呢,CPU就会自动的随机的使用其中的资源。
③两者的区别:
a) 静态内存分配在编译时完成,不占用CPU资源; 动态内存分配在运行时,分配与释放都占用CPU资源。
b) 静态内存在栈(stack)上分配; 动态内存在堆(heap)上分配。
c) 动态内存分配需要指针和引用类型支持,静态不需要。
d) 静态内存分配是按计划分配,由编译器负责; 动态内存分配是按需分配,由程序员负责。
6、研究内存的意义在哪呢?
首先是因为内存是一个程序的可变数据存放的地方,而程序的运行又离不开内存(内存的本质是RAM);其次研究程序要研究数据结构和算法(数据结构中研究的是数据是如何组织的,算法中研究的是如何来加工数据,得到最优速,最小内存的)(程序越复杂数据结构越多,算法越复杂,那么所占用的内存就会越多),所以研究内存的意义就在于如何来简化数据结构和简化算法,使得程序的执行速度相同的情况下,所占用的空间最少。
7、为什么要管理内存?怎样管理内存?
为什么要管理内存呢?因为计算机内存的容量越大,出现的可能性就越多,不管理内存的话可能运行程序的时候对内存的消耗就非常的大,当内存消耗完之后,程序就跑飞了,怎么解决的呢?就是程序不断的调用内存然后再释放内存,通过反复的调用释放来解决的。而这个反复调用内存和释放内存的过程就是对内存的管理。
从编程语言的角度来解析程序对内存的管理:
汇编语言:汇编语言通过对内存地址的操作,将每一句具体的指令放在一个具体的内存地址下去执行,从而实现了对内存的管理。
C语言: C语言是通过编译器来实现内存的管理的,对于程序员而言是通过malloc(实现的是申请内存)和free(释放内存)来实现的 。(通过编译器提供的变量名来访问内存,对于大容量的内存是通过这两个来实现的(这两个的本质是一个API),而内存的管理还是通过编译器来实现)C++语言:C++使用的new来创建一个内存单元,待到用完之后再通过delete来释放内存。
从系统的角度来理解对内存的管理:
在有操作系统的硬件中,操作系统就自动的把内存分成了很多的块,一个块呢就是一页,(一般是4kb),然后再一页一页的来管理。
对于没有操作系统的硬件来说呢(比如裸机):
程序就直接操作内存,所需要的内存呢就需要程序员自己来计算和安排。