C语言基础9编程高级用法

目录

一、GNU工具

二、GCC编译器

1、基本介绍

2、gcc所支持后缀名解释

3、编译器的主要组件

4、GCC的基本用法和选项 

5、GCC的错误类型及对策 

5、GCC使用实例

三、GDB调试工具

四、条件编译

 一、根据宏是否定义,其语法如下:

二、根据宏的值,其语法如下:

五、结构体

1、简述

2、概念

3、定义

4、说明

5、大小

6、结构体变量的使用形式

总结:

六、结构体数组

1、结构体数组的定义

(1)先定义结构体类型,再用它定义结构体数组。

(2) 在定义结构体类型同时定义结构体数组。

(3)直接定义结构体数组

2、结构体数组的初始化

七、结构体指针

(1)结构体指针在程序中的一般定义形式为:

(2)结构体指针访问

八、共用体及typedef

1、共用体的概念

2、union和struct主要区别

3、typedef的作用

九、内存管理

1、存储模型

2、管理

(1)动态内存

(2)动态存储分配

(3)堆内存的分配与释放

(4)防止野指针:


一、GNU工具

名词解释:

  编译工具:把一个源程序编译为一个可执行程序
  调试工具:能对执行程序进行源码或汇编级调试
  软件工程工具:用于协助多人开发或大型软件项目的管理,如make、CVS、Subvision
  其他工具:用于把多个目标文件链接成可执行文件的链接器,或者用作格式转换的工具。

部分相关资源可供查看:
  http://www.gnu.org/
  http://gcc.gnu.org/
  http://www.kernel.org/
  http://www.linux.org/
  http://www.linuxdevices.com/
  http://sourceforge.net/index.php

二、GCC编译器

1、基本介绍

全称为GNU CC ,GNU项目中符合ANSI C标准的编译系统   编译如C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言GCC是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%一个交叉平台编译器 ,适合在嵌入式领域的开发编译。

GCC编译器的版本  8.2
GNU Compiler Collection
C, C++, Objective-C, Fortran, Java, Ada
http://gcc.gnu.org

2、gcc所支持后缀名解释

 .c  C原始程序
.C/.cc/.cxx C++原始程序
.m  Objective-C原始程序
.i  已经过预处理的C原始程序
.ii  已经过预处理的C++原始程序
.s/.S 汇编语言原始程序
.h  预处理文件(头文件)
.o  目标文件
.a/.so 编译后的库文件

3、编译器的主要组件

分析器:分析器将源语言程序代码转换为汇编语言。因为要从一种格式转换为另一种格式(C到汇编),所以分析器需要知道目标机器的汇编语言。
汇编器:汇编器将汇编语言代码转换为CPU可以执行字节码。
链接器:链接器将汇编器生成的单独的目标文件组合成可执行的应用程序。链接器需要知道这种目标格式以便工作。
标准C库:核心的C函数都有一个主要的C库来提供。如果在应用程序中用到了C库中的函数,这个库就会通过链接器和源代码连接来生成最终的可执行程序。

4、GCC的基本用法和选项 

Gcc最基本的用法是∶gcc [options] [filenames] 
  -c,只编译,不连接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。 
  -o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。
  -g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。 
  -O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。
  -O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。
  -I  dirname,将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。
  -L  dirname,将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在链接过程中使用的参数。

5、GCC的错误类型及对策 

第一类∶C语法错误 
错误信息∶文件source.c中第n行有语法错误(syntex errror)。有些情况下,一个很简单的语法错误,gcc会给出一大堆错误,我们最主要的是要保持清醒的头脑,不要被其吓倒,必要的时候再参考一下C语言的基本教材。 
第二类∶头文件错误 
错误信息∶找不到头文件head.h(Can not find include file head.h)。这类错误是源代码文件中的包含头文件有问题,可能的原因有头文件名错误、指定的头文件所在目录名错误等,也可能是错误地使用了双引号和尖括号。 
第三类∶档案库错误 
错误信息∶链接程序找不到所需的函数库(ld: -lm: No such file or directory )。这类错误是与目标文件相连接的函数库有错误,可能的原因是函数库名错误、指定的函数库所在目录名称错误等,检查的方法是使用find命令在可能的目录中寻找相应的函数库名,确定档案库及目录的名称并修改程序中及编译选项中的名称。 
第四类∶未定义符号 
错误信息∶有未定义的符号(Undefined symbol)。这类错误是在连接过程中出现的,可能有两种原因∶一是使用者自己定义的函数或者全局变量所在源代码文件,没有被编译、连接,或者干脆还没有定义,这需要使用者根据实际情况修改源程序,给出全局变量或者函数的定义体;二是未定义的符号是一个标准的库函数,在源程序中使用了该库函数,而连接过程中还没有给定相应的函数库的名称,或者是该档案库的目录名称有问题,这时需要使用档案库维护命令ar检查我们需要的库函数到底位于哪一个函数库中,确定之后,修改gcc连接选项中的-l和-L项。

5、GCC使用实例

//源码保存在文件 test.c
#include 

int main()
{
        printf("hello,world\n");
        return 0;
}


/*
syj@ubuntu:~$ gcc -o test test.c      //编译
syj@ubuntu:~$ ./test                  //执行
hello,world
syj@ubuntu:~$ gcc -v -o test test.c   //查看更详细的信息
*/

gcc编译过程分为四个步骤:

1.预处理(Pre-Processing)

2.编译(Compiling)

3.汇编(Assembling)

4.链接(Linking)

 C语言基础9编程高级用法_第1张图片

生成预处理代码:
$ gcc –E test.c -o test.i 

用wc命令,查看这两个阶段代码大小:
test.i比test.c增加了很多内容,主要是放在系统提供的include文件中的。
$ wc test.c test.cpp
9     16     127   test.c
842 1934   16498 test.cpp
851 1950   16625 总用量
    
生成汇编代码:
检查语法错误,并生成汇编文件
$ gcc –S test.c –o test.s

生成目标代码:
方法一,用gcc直接从C源代码中生成目标代码:
$ gcc –c test.s –o test.o
方法二,用汇编器从汇编代码生成目标代码:
$ as test.s –o test.o

生成可执行程序:
将目标程序链接库资源,生成可执行程序
$ gcc  test.s –o test
./test

三、GDB调试工具

(1) 调试器--Gdb调试流程 
 首先使用gcc对test.c进行编译,注意一定要加上选项‘-g’ 

 # gcc -g test.c -o test 
 # gdb test

(2) Gdb调试流程 
 查看文件

(gdb) l   //显示10行不够再l

设置断点 

(gdb) b 6

删除断店

(gdb)delete breakpoints //删除所有断点
(gdb)d 3                //删除3号断点

查看断点情况 

(gdb) info b

运行代码 

(gdb) r

查看变量值

(gdb) p n  //(p+变量名,查看变量值)

单步运行 

(gdb) n
(gdb) s  //(进入函数)

恢复程序运行 

(gdb) c  (后面接着全部执行)

帮助 

(gdb) help [command]

退出

(gdb) q

(3)Gdb的使用切记点 

  • 在gcc编译选项中一定要加入‘-g’。
  • 只有在代码处于“运行”或“暂停”状态时才能查看变量值。
  • 设置断点后程序在指定行之前停止。

(4)Gdb调试

  • 运行被调试程序,设置所有的能影响该程序的参数和变量。
  • 保证被调试程序在指定的条件下停止运行。
  • 当被调试程序停止时,让开发工程师检查发生了什么。
  • 根据每次调试器的提示信息来做响应的改变,以便修正某个错误引起的问题

四、条件编译

编译器根据条件的真假决定是否编译相关的代码,常见的条件编译有两种方法:
 

 一、根据宏是否定义,其语法如下:

  #ifdef   //ifndef 如果没有定义
     ……
  #else
     ……
  #endif

实例:


  #define  _DEBUG_
  #ifdef  _DEBUG_
  printf(“The macro _DEBUG_ is defined\n”);
  #else
  printf(“The macro _DEBUG_ is not defined\n”);
  #endif

二、根据宏的值,其语法如下:

  #if   //也可以直接0 1
     ……
  #else
     ……
  #endif

实例:

 #define  _DEBUG_   1
 #if  _DEBUG_
 printf(“The macro _DEBUG_ is defined\n”);
 #else
 printf(“The macro _DEBUG_ is not defined\n”);
 #endif

五、结构体

1、简述

  • 在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体。此时,就要用到一种新的构造类型数据——结构体(structure),简称结构。
  • 结构体的使用为处理复杂的数据结构(如动态数据结构等)提供了有效的手段,而且,它们为函数间传递不同类型的数据提供了方便。

2、概念

结构体是用户自定义的新数据类型,在结构体中可以包含若干个不同数据类型和不同意义的数据项(当然也可以相同),从而使这些数据项组合起来反映某一个信息
例如,可以定义一个职工worker结构体,在这个结构体中包括职工编号、姓名、性别、年龄、工资、家庭住址、联系电话。这样就可以用一个结构体数据类型的变量来存放某个职工的所有相关信息。并且,用户自定义的数据类型worker也可以与int、double等基本数据类型一样,用来作为定义其他变量的数据类型。

3、定义

定义一个结构体类型的一般形式为:

  struct  结构体名
 {
  数据类型   成员名1;
  数据类型   成员名2;
  :
  数据类型   成员名n;
  };
  • 在大括号中的内容也称为“成员列表”或“域表”。
  • 其中,每个成员名的命名规则与变量名相同;
  • 数据类型可以是基本变量类型和数组类型,或者是一个结构体类型;
  • 用分号“;”作为结束符。整个结构的定义也用分号作为结束符。

 Example:
 定义一个职工worker结构体如下:

#include 
#include 

int main(){
        //定义结构体
        struct worker{
                long number;
                char name[20];
                char sex;
                int age;              //这个age是成员变量
                char address[80];
        };                           //注意分号不能省略
        
        //初始化
        struct worker wk1;            
        
        //赋值1
        wk1.age = 20;
        int age = 109;                //注意区分,这个age是变量
        wk1.number = 20222022;
        strcpy( wk1.name,"zhangsan"); //ok       
//      wk1.name ="zhangsan";         //error注意不能对指针进行这样赋值
        strcpy( wk1.address,"xx road 10");
        printf("number:%ld name:%s sex:%c age:%d address:%s\n",wk1.number,wk1.name,wk1.sex,wk1.age,wk1.address);   //打印

        //赋值2
        struct worker wk2 = {20222023,"zhaosi",'0',20,"东城区"};

        //赋值3
        struct newworker{
                long number;
                char name[20];
                char sex;
                int age;
                float salary;
                char address[80];
        }wk3={20222024,"wangwu",'1',30,2000,"shanghai"},wk4={20220225};

}

结构体名字可以省略,不建议。除非像赋值3,直接定义变量可以省略。

注意:

“struct worker”代表类型名,不能分开写为:

struct worker1,worker2;         //错误,没有指明是哪种结构体类型
worker worker1,worker2;         //错误,没有struct关键字
                                //系统不认为worker是结构体类型 

为了使用上的方便,程序员通常用一个符号常量代表一个结构体类型。
即在程序开头加上下列语句:
 #define WORKER struct worker;
这样在程序中,WORKER与struct worker完全等效。

#define WORKER struct worker;

//Example:
WORKER
{
    long number;
    char name[20];
    char sex;
    int age;
    float salary;
    char address[80];
    char phone[20];  
};

//此时,可以直接用WORKER定义worker1、worker2两个变量,而不必再写关键字struct。
WORKER worker1,worker2;

4、说明

结构体类型中的成员名可以与程序中的变量名相同,二者并不代表同一对象,编译程序可以自动对它们进行区分。 
最后,总结一下结构体类型的特点:

  • 结构体类型是用户自行构造的。
  • 它由若干不同的基本数据类型的数据构成。
  • 它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,只有用它定义变量时才分配空间。

5、大小

一个结构体变量占用内存的实际大小,也可以利用sizeof求出。它的运算表达式为:
 sizeof(运算量)          //求出给定的运算量占用内存空间的字节数
其中运算量可以是变量、数组或结构体变量,可以是数据类型的名称。 
例如:
sizeof(struct worker) 也可以测试变量 sizeof(wk1)
sizeof(worker1)

#include 

int main(){
        struct worker{
                long number;                  //4  ****
                char name[10];                //10 **** **** ****字节对齐补齐2个,目前总计16
                char sex;                     //1  * 因为上面补齐了,总计还是16               
                int age;                      //4  **** 总计20
                char address[80];             //80 80个*,总计100
        };//
        printf("%d\n",sizeof(struct worker));

}

//结果100

6、结构体变量的使用形式

结构体变量是不同数据类型的若干数据的集合体。在程序中使用结构体变量时,一般情况下不能把它作为一个整体参加数据处理,而参加各种运算和操作的是结构体变量的各个成员项数据。

结构体变量的成员用以下一般形式表示:
结构体变量名.成员名
例如,上节给出的结构体变量worker1具有下列七个成员:

worker1.number;
worker1.name;
worker1.sex;
worker1.age;
worker1.salary;
worker1.address;
worker1.phone;  

在定义了结构体变量后,就可以用不同的赋值方法对结构体变量的每个成员赋值。例如:

strcpy(worker1.name,”Zhang San”);
worker1.age=26;
strcpy(worker1.phone,”1234567”);
worker1.sex=’m’;

除此之外,还可以引用结构体变量成员的地址以及成员中的元素。

例如:引用结构体变量成员的首地址

&worker1.name;

 引用结构体变量成员的第二个字符

worker1.name[1];

引用结构体变量的首地址

&worker1;

 注意:

  • (1)不能将一个结构体类型变量作为一个整体加以引用,而只能对结构体类型变量中的各个成员分别引用。例如,对上面定义的结构体类型变量wan,下列引用都是错误的:
cout<>wan;      //error
//但是可以如下引用:
cout<>wan.name; //ok
 
  • (2)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员。只能对最低级的成员进行赋值或存取以及运算。例如,对上面定义的结构体类型变量worker1,可以这样访问各成员:
worker1.age
worker1.name
worker1.birthday.year
worker1.birthday.month
worker1.birthday.day

注意:不能用worker1.birthday来访问worker1变量中的成员birthday,因为birthday本身是一个结构体变量。 

  •      (3)对成员变量可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。例如:
    worker2.age=worker1.age;
    sum=worker1.age+worker2.age;
    worker1.age++;
  •      (4)在数组中,数组是不能彼此赋值的,而结构体类型变量可以相互赋值。  在C程序中,同一结构体类型的结构体变量之间允许相互赋值,而不同结构体类型的结构体变量之间不允许相互赋值,即使两者包含有同样的成员

总结:

结构体解决的是什么问题? 举个例子:一个学生的姓名、学号、成绩,普通类型无法满足的时候,需要自己设计一个类型。

六、结构体数组

1、结构体数组的定义

  • 具有相同结构体类型的结构体变量也可以组成数组,称它们为结构体数组。结构体数组的每一个数组元素都是结构体类型的数据,它们都分别包括各个成员(分量)项。定义结构体数组的方法和定义结构体变量的方法相仿,只需说明其为数组即可。

可以采用三种方法:

(1)先定义结构体类型,再用它定义结构体数组。

结构体数组的定义形式如下:
     struct 结构体名 
    {
        成员表列;
     };
       struct 结构体名 
 例如:

struct student
{
    char name[20];
    char sex;
    int age;
    char addr[20];
};

struct student stu[3];

(2) 在定义结构体类型同时定义结构体数组。

结构体数组的定义形式如下:
     struct 结构体名 
     {
        成员表列;
    }数组名[元素个数];
 例如:

struct student
{
    char name[20];
    char sex;
    int age;
    char addr[20];
}stu[3];

(3)直接定义结构体数组

   结构体数组的定义形式如下:
    struct          //没有结构体名
    {
        成员表列;
         }数组名[元素个数]; 
 例如:

struct 
{
    char name[20];
    char sex;
    int age;
    char addr[20];
}stu[3];

2、结构体数组的初始化

结构体数组在定义的同时也可以进行初始化,并且与结构体变量的初始化规定相同,只能对全局的或静态存储类别的结构体数组初始化。
结构体数组初始化的一般形式是:

struct 结构体名
{
    成员列表;
};

struct 结构体名 数组名[元素个数]={初始数据表};

或者:

struct 结构体名
{
    成员表列;
}数组名[元素个数]={初始数据表};  

由于结构体变量是由若干不同类型的数据组成,而结构体数组又是由若干结构体变量组成。所以要特别注意包围在大括号中的初始数据的顺序,以及它们与各个成员项间的对应关系。

七、结构体指针

可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。

结构体指针与前面介绍的各种指针变量在特性和方法上是相同的。与前述相同,在程序中结构体指针也是通过访问目标运算“*”访问它的对象。

(1)结构体指针在程序中的一般定义形式为:

struct 结构体名  *结构指针名;
其中的结构体名必须是已经定义过的结构体类型。 

例如,对于上一节中定义的结构体类型struct student,可以说明使用这种结构体类型的结构指针如下:

struct student *pstu;

其中pstu是指向struct student结构体类型的指针。结构体指针的说明规定了它的数据特性,并为结构体指针本身分配了一定的内存空间。但是指针的内容尚未确定,即它指向随机的对象。

(2)结构体指针访问

当表示指针变量p所指向的结构体变量中的成员时,“(*结构体指针名).成员名”这种表示形式总是需要使用圆括号,显得很不简炼。因此,对于结构体指针指向的结构体成员项,给出了另外一种简洁的表示方法,如下表示:

结构体指针名->成员名

它与前一种表示方法在意义上是完全等价的。
例如,结构体指针p指向的结构体变量中的成员name可以表示如下:

(*p).name 或 p->name

八、共用体及typedef

1、共用体的概念

在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体。共用体在定义、说明和使用形式上与结构体相似。两者本质上的不同仅在于使用内存的方式上。 
定义一个共用体类型的一般形式为:

union 共用体名
{
    成员表列;
};

例如:

#include 

int main(){
        union gy
        {
                int i;    //4
                char c;   //1
                float f;  //4
        };
        printf("%d\n",sizeof(union gy));

}
//结果4

这里定义了一个共用体类型union gy,它由三个成员组成,这三个成员在内存中使用共同的存储空间。由于共用体中各成员的数据长度往往不同,所以共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间

在程序中经常使用结构体与共用体相互嵌套的形式。即共用体类型的成员可以是结构体类型,或者结构体类型的成员是共用体类型。
例如,下列结构体类型datas的第三个成员是共用体类型: 

struct datas
{
    char *ps;
    int type;
    union
    {
        float fdata;
        int idata;
        char cdata;
    }udata;
}n;

//访问
n.udata.cdata

2、union和struct主要区别

(1)struct和union都是由多个不同的数据类型成员组成, 但在任何同一时刻, union中只存放了一个被选中的成员; 而struct的所有成员都存在。在struct中,各成员都占有自己的内存空间,它们是同时存在的,一个struct变量的总长度等于所有成员长度之和,遵从字节对其原则; 在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在 , Union变量的长度等于最长的成员的长度。

(2)对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了,所以,共同体变量中起作用的成员是最后一次存放的成员; 而对于struct的不同成员赋值是互不影响的。

3、typedef的作用

这样写类型比较麻烦,所以要学typedef。在C语言中经常在定义结构体类型时使用typedef,例如

typedef struct _node_    //或者typedef struct
{
    int  data;
    struct _node_ *next;
}listnode, *linklist;

这里定义了两个新的数据类型listnode和linklist。其中listnode等价于数据类struct _node_ 而 linklist等价于struct _node_ *
 

listnod *p; //等价于下面这种定义指针
linklist q;//等价于typedef (int *) INT_P; 或char float都可以,需要理解
q = &linknode

首先介绍大家比较熟悉的typedef

int i;//定义一个整型变量i
typedef  int myInt;
myInt j;//定义一个整型变量j

下面首先介绍一下函数指针。函数指针的形式:

形式1:返回类型(*函数名)(参数表) 

#include 
 
using namespace std;
//定义一个函数指针pFUN,它指向一个返回类型为char,有一个整型的参数的函数
char (*pFun)(int);
//定义一个返回类型为char,参数为int的函数
//从指针层面上理解该函数,即函数的函数名实际上是一个指针,
//该指针指向函数在内存中的首地址
char glFun(int a)
{
    cout << a;
    //return a;
}
 
int main()
{
//将函数glFun的地址赋值给变量pFun
    pFun = glFun;
//*pFun”显然是取pFun所指向地址的内容,当然也就是取出了函数glFun()的内容,然后给定参数为2。
    (*pFun)(2);
    return 0;
}

形式2:typedef  返回类型(*新类型)(参数表)

通过上面的一个小例子,我们知道了函数指针的用法,而typedef可以让函数指针更直观方便

typedef char (*PTRFUN)(int); 
PTRFUN pFun; 
char glFun(int a){ return;} 
void main() 
{ 
    pFun = glFun; 
    (*pFun)(2); 
}

typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回char类型。后面就可以像使用int,char一样使用PTRFUN了。第二行的代码便使用这个新类型定义了变量pFun,此时就可以像使用形式1一样使用这个变量了。

九、内存管理

1、存储模型

C语言基础9编程高级用法_第2张图片

2、管理

(1)动态内存

C/C++定义了4个内存区间:代码区/全局变量与静态变量区/局部变量区即栈区/动态存储区,即堆区。

  • 静态存储分配。通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。
  • 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(2)动态存储分配

  • 有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为。
  • 所有动态存储分配都在堆区中进行。
  • 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

(3)堆内存的分配与释放

  • 当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。
  • 堆区是不会自动在分配时做初始化的(包括清零),所以必须用初始化式(initializer)来显式初化。
  • malloc/free  //两个函数成对出现  man查看
void * malloc(size_t num)

void   free(void *p)

a.  malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。 

b.  malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。

c.  malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。 

d.  如果free的参数是NULL的话,没有任何效果

e.  释放一块内存中的一部分是不被允许的。

案例:

#include 
#include 

int main(int argc, const char *argv[])
{
        char *p = NULL;
        p = (char *)malloc(10);   //如果不分配会出现错误
        strcpy(p,"hello");
        printf("%s\n",p);
        free(p);
        return 0;
}

注意事项:

  • 删除一个指针p(free(p);)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针
  • 动态分别分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。
  • cmalloc和free是配对使用的,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。

(4)防止野指针:

野指针不是NULL指针,是指向“垃圾“内存的指针,对计算机有危险。
主要成因有2个:

  • 指针变量没有被初始化
  • 指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。指针操作超越了变量的作用范围。这种情况让人防不胜防。
  • 优化:判断是否是野指针,如果是不需要分配空间,回收时再指向NULL

上述案例优化

#include 
#include 

int main(int argc, const char *argv[])
{
        char *p = NULL;
        p = (char *)malloc(10);
        if(NULL == p)
        {
                printf("malloc failed\n");
                return -1;
        }
        strcpy(p,"hello");
        printf("%s\n",p);
        free(p);
        p = NULL;
        return 0;
}

你可能感兴趣的:(c语言,开发语言,物联网)