Linux基础——“C语言高级编程” C语言中的细节你真的知道吗?
文章目录
- GCC简介
- 一、GCC编译的过程
- 1. 预处理:
- 2.编译:
- 3.汇编处理
- 4. 链接
- 二、GDB调试工具
- 1. 调试器——GDB调试流程
- 2. GDB的使用切记点:
- 3. 条件编译
- 三、结构体
- 1. 结构体数组
- 2. 结构体指针
- 四、共用体 和 typedef
- 1. 共用体和结构体的区别
- 2. typedef
- 五、内存管理
- 1. 动态内存
- 2. malloc / free 注意事项
- 3. 内存泄漏:
- 4. 野指针
- 5. malloc如何使用?
GCC简介
gcc 所支持后缀名解释
.c | c原始程序 |
---|---|
.C / .cc /.cxx | C++原始程 |
.m | Objective—C原始程序 |
.i | 已经过预处理的C原始程序 |
.ii | 已经过预处理的C++原始程序 |
.s/.S | 汇编语言原始程序 |
.h | 预处理文件(头文件) |
.o | 目标文件 |
.a/.so | 编译后的库文件 |
一、GCC编译的过程
GCC的编译流程分为四个步骤:
- 预处理
- 编译
- 汇编
- 链接
#include<stdio.h>
#include<math.h>#define N 10
#define _DEBUG_int main(int argc ,char **argv)
{double m =615;double n;m += N;n = sqrt(m);
#ifdef _DEBUG_printf("debug:m=%lf n=%lf\n",m,n);
#elseprintf("release:m=%lf n=%lf\n",m,n);
#endifreturn 0;}
1. 预处理:
把程序中 # 开头的语句进行处理 ,生成文件 .i
把头文件和宏都展开,判断语句,把判断结果我语句保留.
我们来查看一下:
gcc -E gccc.c -o gccc.i
2.编译:
把预处理文件 .i 进行处理,生成汇编文件 .s,这个过程会检测代码的错误。
gcc -S gccc.i -o gccc.s
3.汇编处理
把汇编文件 .s 进行汇编处理,生成目标文件 .o
4. 链接
将目标程序连接库资源,生成可执行程序
#gcc gccc.s -o gccc
#./gccc
二、GDB调试工具
1. 调试器——GDB调试流程
首先使用gcc对test.c 进行编译,注意一定要加上选项 “ -g ”
#gcc -g test.c -o test
#gdb test
注意:gdb是在你运行时候,发现结果不对,当你编译时候出现错误,gdb是无法调试的。
GDb调试流程 | 说明 |
---|---|
-(gdb)l | 查看文件 |
-(gdb)b +行数 | 设置断点 |
-(gdb)info b | 查看端点情况 |
-(gdb)r | 运行代码 |
(gdb) p n | 查看变量值 |
(gdb)n / (gdb) s | 单步运行 |
(gdb)c | 恢复程序运行(断点调换) |
(gdb)help [command] | 帮助 |
程序文件和可执行文件大小是不同的,可执行文件大很多。
2. GDB的使用切记点:
- 在gcc编译选项中一定要加入“ -g ”
- 只有在代码处于 “ 运行 ” 和 “ 暂停 ” 状态时才能查看变量值
- 设置断点后程序在指定行之前停止。
若我们在gdb如何调试参数呢?
# (gdb) set args X X
3. 条件编译
编译器根据条件的真假决定是否编译相关的代码
常见的条件编译有两种方法:
根据宏是否定义,其语法如下:
#ifdef<macro> //如果定义一个宏,执行一段代码,.....#else //否则执行其他代码....#endif
根据宏的值,其语法如下:
#if<macro> ......#else .....#endif
三、结构体
一般形式:
struct 结构体名
{数据类型 成员1;
数据类型 成员2;
数据类型 成员3;
...
数据类型 成员n;
};
说明:
- 结构体类型是用户自行构造的;
- 它由若干个不同的基本数据类型的数据构成
- 它属于c语言的一个数据类型,与整型,实型相当。因此,定义它时不分配空间,只有用它定义变量时 才分配空间。
1. 结构体数组
结构体数组的定义:
- 具有相同结构体类型的结构体变量构成的数组,为结构体数组。结构体数组的每一个数组元素都是结构体类型的数据,他们都包括各个成员(分量)项。
- 定义结构体数组的方法和定义结构体变量的方法相仿,只需说明其为数组即可。可以采用三种方法:
第一种:先定义类型在定义数组
struct 结构体名
{成员列表;
};
struct 结构体名
例如:
struct student
{char name[20];
char sex;
int age;
char addr[20];
};
struct student stu[3];
第二种:先定义类型同时定义数组
struct 结构体名
{成员列表;
}数组名[元素个数];
例如:
struct student
{char name[20];
char sex;
int age;
char addr[20];
}stu[3];
第三种:直接定义结构体数组
struct //没有结构体名
{成员列表;
}数组名[元素个数];
例如:
struct
{char name[20];
char sex;
int age;
char addr[20];
}stu[3];
结构体数组赋初始值:
#include<stdio.h>
#include<string.h>
#define N 20
struct student
{int no;
char name[N];
float socre;
}s2[5];int main (int argc ,const char *argv[])
{int i;struct student s1[]={{1,"s1",90},{2,"s2",88},{3,"s3",94}};s2[0] = s1[0];s2[1] = s1[1];s2[2] = s1[2];for(i=0;i<sizeof(s1)/sizeof(struct student);i++){printf("%d-%s-%f",s1[i].no,s1[i].name,s1[i].socre);}return 0;
}
2. 结构体指针
结构体指针:
可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。
一般形式:
struct 结构体名 *结构体指针名;
其中的结构体名必须是已经定义过的结构体类型。
例如:
对于刚才中定义的结构体类型struct student ,可以说明使用这种结构体类型的结构体指针如下:
struct student *pstu;
其中pstu是指向struct student结构体类型的指针。结构体指针的说明规定了它的数据特性,并为结构体指针本身分配了一定的内存空间。但是指针的内容尚未说明,即它指向随机的对象。
结构体指针访问成员
#include<stdio.h>
#include<string.h>
#define N 20
struct student
{int no;
char name[N];
float socre;
};int main (int argc ,const char *argv[])
{int i;struct student s1[]={{1,"s1",90},{2,"s2",88},{3,"s3",94}};struct student *p;p=s1;p=&s1[0];for(i=0;i<sizeof(s1)/sizeof(struct student);i++){//printf("%d-%s-%f\n",s1[i].no,s1[i].name,s1[i].socre);printf("===%d-%s-%f====\n",p->no,p->name,(*p).socre);p++;}return 0;
}
把上述代码输出封装成函数:
#include<stdio.h>
#include<string.h>
#define N 20
struct student
{int no;
char name[N];
float socre;
};void printf_into(struct student *p , int n){ //struct student *p 指向 s1 int i;for(i=0;i<n;i++){ //指针访问成员的不同形式 printf("===%d-%s-%f====\n",p->no,p->name,(*p).socre);printf("===%d-%s-%f====\n",p[i].no,p[i].name,p[i].socre);printf("===%d-%s-%f====\n",(p+i)->no,(p+i)->name,(p+i)->socre);p++;}
}int main (int argc ,const char *argv[])
{struct student s1[]={{1,"s1",90},{2,"s2",88},{3,"s3",94}};struct student *p;printf_into(s1,sizeof(s1)/sizeof(s1[0]));return 0;
}
四、共用体 和 typedef
共用体的感念:
不同数据类型的数据可以使用共同的存储区域。这种数据构造类型称为共用体,简称共用,又称联合体。
一般形式:
union 共用体名
{成员表列;};
例如:
union gy
{int i;char c;float f;
};
定义了一个共用体类型union gy , 它由三个成员组成,这三个成员在内存中使用共同的存储空间,由于共用体中各个成员的数据长度往往不同,所以共用体百年来在存储时总是按其成员中数据长度最大的成员占用内存空间。
1. 共用体和结构体的区别
共用体和结构体的区别在于:使用内存的方式上。
struct gy { int i; char c; float f; };
结构体类型的变量占用内存大小为 4+1+4 =9 个字节
union gy { int i; char c; float f; };
共用体类型的变量占用内存大小为 4 (数据长度最大的成员) 个字节
共用体的定义和结构体数组的方法和定义结构体变量的方法相仿,也可以采用三种方法。
union gy
{int a;short b;int c;
}v2;int main (int argc ,const char *argv[])
{union gy v1;printf("%d %d %d\n",sizeof(char),sizeof(short),sizeof(int));printf("%d \n",sizeof(union gy));v1.a='A';v1.b=20;v1.c=0x13245678;printf("%#x %c\n",v1.a ,v1.a);printf("%p %p %p\n",&v1.a ,&v1.b ,&v1.c);return 0;
}
在使用共用体类型变量的数据时候要注意:
在共用体类型变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去了作用。
如以下赋值语句;
a.i=1;
a.c='a';
a.f =1.5;
完成以上三个赋值运算以后,a.f是有效的,a.i和a.c已经无意义了
共用体
在程序经常使用结构体与共用体相互嵌套的形式。 即共用体类型的成员可以是结构体类型,或者结构体类型的成员是共用体类型。
例如:结构体类型datas的第三个成员是共用体类型
struct datas
{char *ps;int type;union{float fdata;int idata;char cdata;}udata;
};
2. typedef
在c中,允许使用关键字typedef定义新的数据类型
语法如下:
typedef <己有数据类型> <新数据类型>;
如:
typedef int INTEGER ;
这里新定义了数据类型INTEGER ,其等价于int
INTEGER i; 等同于 int i;
typedef 我们经常用在结构体的定义上,例如
typedef struct _node_
{int data;struct_node_ *next;
}listnode, *linklist;
这里定义了两个新的数据类型listnode
和 linklist
。 其中listnode
等价于数据类型struct _node_
而 linklist
等价于struct_node_*
#include<stdio.h>typedef struct node {int data;struct node *next;
}listnode , *linklist;int main()
{//struct node n1,n2,n3,*p;
//listnode n1,n2,n3, *p;
listnode n1 ,n2 ,n3;
linklist p;n1.data = 10;
n1.next = NULL;n2.data = 11;
n2.next = NULL;n3.data = 12;
n3.next = NULL;p=&n1;
return 0;
}
五、内存管理
1. 动态内存
c/c++定义了4个内存区间;
- 代码区 ——存放代码
- 全局变量与静态变量区 ——静态存储区
- 局部变量区
- 动态存储区(堆区)
栈区——静态存储分配
通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。
在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
特点:存储周期长,空间默认是清零
堆区——动态存储区
有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为。
所有动态存储分配都在堆区中进行。
从堆上分配,亦称动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
堆内存的分配与释放
当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得
堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能 对该堆空间进行再次分配,做到重复使用有限的资源 。堆区是不会自动在分配时做初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。
malloc
void malloc(size_t num) free
vold free(void *p)
- malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
- malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。
- malloc返回值的类型是
void *
,所以在调用malloc时要显式地进行类型转换,将void *
转换成所需要的指针类型。 - 如果free的参数是NULL的话,没有任何效果。
- 释放一块内存中的一部分是不被允许的。
#include<stdio.h>
#include<stdlib.h>int main()
{char *p;p=(char *)malloc(10 * sizeof(char));if(p == NULL){printf("malloc failed\n");return 0;}printf("p=%p\n",p);return 0;
}
2. malloc / free 注意事项
- 删除一个指针
p( free(p);)
,实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针,这样的指针非常危险,最好在free后置空
free(p); p=NULL;
动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。 - malloc与free是配对使用的,free只能释放堆空间。如果malloc返回的指针值丢失,则所分配的堆空间无法回收,称"内存泄漏",同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。
- 动态分配的变量或对象的生命期(分配空间到释放)。无名对象的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为自由空间(free store)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错。简单说就是,malloc写在函数内,free写在函数外,这是一个非常容易出错的点。要保证return 的起始地址正常接收,保证地址准确复制
出错的例子:
#include<stdio.h>
#include<stdlib.h>char *get_string() {//char s[] = "welcome";//static char s[] = "welcome";//char *s = "welcome";char *s;s=(char *)malloc(10 * sizeof(char));if(s == NULL){printf("malloc failed\n");return 0;}s = "welcome";//printf("input");//scanf("%s",s);//printf("s=%s\n",s);return s;
}int main()
{char *p;p = get_string();printf("%s\n",p);free(p);p = NULL;return 0;
}
错误原因:
3. 内存泄漏:
如果malloc返回的指针值丢失,则所分配的堆空间无法回收,称 "内存泄漏 "
程序刚跑起来,很好,跑了几个小时,或者几天,或者几周,程序就崩溃了! malloc申请的空间,程序不会主动释放,linux中的话,程序结束后,系统会回收这个空间。
如何避免:
1.注意,循环中有没有一直申请
2.及时合理的释放free§; p=NULL;
4. 野指针
不是NULL指针,是指向 “垃圾” 内存的指针。“野指针”是很危险的。
野指针的成因主要有两种:
- 指针变量没有被初始化。
int *p;
//这是野指针- 指针p被free之后,没有置为NULL,让人误以为p是合法指针。
- 指针操作超越了变量的作用范围。这种情况让人防不胜防。
1.int *p = NULL; //这是正常的指针定义
2.int *p; //这是野指针
野指针导致段错误!!的代码,不要效仿,但是要了解错在哪,避免错误
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main()
{char *p;strcpy(p,"welcome");return 0;
}
导致段错误的原因是,p指向了一个随机的地址,这个地址并不是程序可以访问的空间,这时候你强行写内容到里面,会导致段错误。野指针的非法访问,系统会把这个进程杀死。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main()
{char *p;char s[]="wel"; // s里面有 w e l \0 (4个字节)p = s;strcpy(p,"welcome");return 0;
}
结果也是错误,因为也是一种非法访问,s数组里只有4个字节,把大于4个字节的字符强行写入数组,会导致错误。
5. malloc如何使用?
指针变量 = (强制类型)malloc(大小)
如:int *p = (int *)malloc (3 * sizeof ( int ) ); //也是一种数组的定义方式
free(指针变量名);
free是释放内存空间的函数,通常与申请内存空间的函数malloc()结合使用,可以释放由malloc(); 、calloc();、realloc();等函数申请的内存空间。
Linux基础——“C语言高级编程” C语言中的细节你真的知道吗?相关推荐
- C语言高级编程:预处理中的 # 和 ##
1. 说明 #: 字符串化,使用时需加"" ##:组合2个C语言标识符(token) 测试平台:64位 X86 Ubuntu 2. 代码 #include <stdio.h& ...
- matlab高级教程教材,MATLAB语言高级编程 PDF_IT教程网
资源名称:MATLAB语言高级编程 PDF 本书共分8章,主要介绍了matlab的概述.matlab安装与工作桌面:matlab的编程基础,包括matlab的变量.matlab的运算符.矩阵的创建及运 ...
- Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)
Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...
- 鼠标绘图 c语言,c语言高级编程技术教程 图形显示方式与鼠标输入.doc
c语言高级编程技术教程 图形显示方式与鼠标输入 c语言高级编程技术教程 图形显示方式和鼠标输入 图形显示方式和鼠标输入 问题的提出编写程序,使用鼠标进行如下操作:按住鼠标器的任意键并移动,十字光 标将 ...
- c语言高级程序设计第五版PDF,C语言高级编程.pdf
C语言高级编程 概述 由几个测试程序说开去 预编译与宏 高级预编译介绍 宏的高级用法 变量 变量分类详细解析 我的变量去哪儿了? 大小端对变量的影响 内存与指针 常见内存使用错误大观 指针,又是指针! ...
- 高级编程中C语言属于,c语言高级编程
c语言高级编程 C高级编程 责任编辑:admin 更新日期:2005-8-6 深入了解C语言(函数的参数传递和函数使用参数的方法) tangl_99(原作) 关键字 C语言,汇编,代码生成,编译器 C ...
- 《go语言圣经》+《Mastering.GO-cn》+《go语言高级编程》PDF下载
公众号[爱吃橙子的搬砖小徐]开通啦,后续将会同步更新,欢迎订阅 回复[java面试]获得两套面试宝典 回复[golang]获得go语言学习三部曲 <go语言圣经>+<Masterin ...
- Linux基础篇--shell脚本编程基础
Linux基础篇–shell脚本编程基础 本章内容概要 编程基础 脚本基本格式 变量 运算 条件测试 配置用户环境 7.1 编程基础 程序:指令+数据 程序编程风格: 过程式:以指令为 ...
- C语言高级教程-C语言数组(六):变长数组
C语言高级教程-C语言数组(六):变长数组 一.本文的编译环境 二.一维数组在执行期间确定长度 三.二维数组在执行期间确定长度 四.一维变长数组实例 五.完整程序 5.1 Main.h 文件程序 5. ...
最新文章
- 分治、动态规划、贪婪 之 算法分析
- 白话数字签名(番外篇)----签名EXE文件(下)
- ftp 530 linux,Linux启动ftp服务器530 Permission denied解决方法
- linux驱动之可加载模块
- (Object detection)目标检测从入门到精通——第五部分YOLO 算法
- sot23-6 随机数生成芯片,i2c接口
- 北京理工大学计算机基础实验,北京理工大学2020计算机基础考研真题
- 前照灯检测仪_前照灯检测仪
- Debian10安装Chromium浏览器
- png格式怎么转换?如何修改图片的格式?
- 使用ZYNQ实现单LUT内容的动态修改(一)PL端OOC设计流程
- python空行分隔代码_python空行分隔
- JavaScript学习(五)
- 16.光照(平行光)
- 华硕ZenFone 7系列旗舰5G智能手机凭借Pixelworks显示技术展现惊人的视觉效果
- 【100个 Unity小知识点】☀️ | Unity 中怎样读取Excel文件
- 用fock实现简易shell(程序替换)
- java小光棍数_java做题记录第4期
- 扩增子16S分析专题研讨论会——背景介绍
- Unity绿背景抠图插件