标签 c 下的文章

c预处理指令的使用

无参宏定义的语法

#define 标识符(宏名) [字符串(宏体)]

  • 功能:定义可在程序中使用宏,宏的内容由字符串(宏体)替代
  • 宏体可缺省,表示宏名定义过或取消宏体
  • 示例
#define YES 1
#define NO 0
#define OUT printf("Hello world\n")
#define WIDTH 80
#define LENGTH (WIDTH + 40)
var = LENGTH * 2;
宏展开: var = (80 + 40) * 2;

宏的移除语法

#undef 宏名

  • 功能:删除前面定义的宏
  • 示例
#undef PI
#undef OUT
#undef YES
#undef NO

宏定义的规则

  • #表示这是一条预处理指令,以#开头的均为预处理指令
  • #define为宏定义指令,标识符为所定义的宏名
  • 宏名一般习惯用大写字母表示,以便与变量名相区别
  • 宏定义不是c语句,不必在末尾加分号
  • 字符串(宏体)可以是常数,表达式,格式化字符串等。对于数值表达式进行求值的宏定义应该使用括号
  • 在进行宏定义时,可以引用已定义的宏名,可以层层替换
  • 宏替换只做字符串替换,不分配内存空间,不作正确性检查
  • 宏的有效范围为定义之后到本源文件结束,可以用#undef指令终止宏定义的作用域
  • 宏定义的内容为字符串时,要以双引号扩起来,以便与已定义的宏作区分

带参宏定义

示例

#define MAX(a,b) (a > b ? a : b)
int main(void)
{
        printf("%d\n",MAX(1,2));
        return 0;
}

带参宏定义和带参函数很相似,但有本质上的不同

对比带参宏定义带参函数
处理时间编译时程序运行时
参数类型无类型问题定义实参和新参类型
处理过程不分配内存,简单的字符串替换分配内存先求实参值再传给形参
程序长度变长不变
运行速度不占运行时间调用和返回时间
支持递归不支持支持

c的预处理

c的编译流程

  1. 预处理阶段
  2. 编译阶段
  3. 汇编阶段
  4. 链接阶段
  5. 运行可执行文件

查看预处理阶段的变化

源文件 src/pretreatment.c

/* src/pretreatment.c */
#include "pretreatment.h"
#define HAHAHA 1;
typedef unsigned int uint;
int main(void)
{
        int a = HAHAHA;
        return 0;
}

源文件 include/pretreatment.h

int pretreatment = 11;

使用gccE选项预编译

gcc -E -o bin/pretreatment.i src/pretreatment.c -I include

编译后内容

# 1 "src/pretreatment.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "src/pretreatment.c"
# 1 "include/pretreatment.h" 1
int pretreatment = 11;
# 2 "src/pretreatment.c" 2

typedef unsigned int uint;
int main(void)
{
 int a = 1;;
 return 0;
}

编译出来的文件还是文本文件,只是对预编译指令作了处理,如把宏替换,把#include用饱含的文件内容替换

预处理的概念

  • 预编译指令:以#开头的为预处理指令,如#define,#include等,在源程序中这些指令都放在函数外,而且一般都放在源文件的前面,他们被称为预处理部分。
  • 所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是c语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
  • 预处理在源代码编译之前对其进行一些文本性质的操作,在源程序编译之前作一些处理,生成扩展的c源程序。

预处理的任务

  • 删除注释
  • 插入被#include指令包含的文件内容
  • 定义和替换由#define指令定义的符号(宏替换或宏展开)
  • 条件编译

预处理指令的格式

  • #开头
  • 占单独书写行
  • 语句尾不加分好

合理的使用预处理,编写的程序便于阅读,修改,移植和调试,也有利于模块化的设计

宏定义的概念

  • 在c程序源程序中允许使用一个标识符来表示一个字符串,成为,在预处理时,对程序中所有出现的宏,都用宏定义的字符串去替换,这称为宏替换或宏展开
  • 宏定义是由源程序中的宏定义指令完成的,宏替换是由预处理程序自动完成的
  • 在c语言中,宏定义分为无参宏定义和带参宏定义

常见内存错误及避免

使用未分配成功的内存

在使用内存之前检查指针是否为NULL

引用分配成功但尚未初始化的内存

赋初值,即使是赋零值也不能省略

内存分配成功并且已经初始化,但操作越过了内存的边界

注意下标的使用不能越过边界

忘记释放内存,造成内存泄漏

动态内存的申请与释放必须配对,程序中mallocfree的使用次数一定要相同,否则会出现问题

释放了内存却继续使用它

使用free释放了内存后。将指针设置为NULL。若没有设置为NULL,就会产生野指针,它是指向“垃圾”内存的指针。


内存错误的注意点

指针消亡了,并不代表它所指的内存会被自动释放
内存被释放了,并不代表指针会消亡或者成了NULL指针

野指针

野指针的形成是指针表辆没有初始化,任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值时随机的,他会乱指一气
指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
使用free释放了内存之后,将指针设置为NULL。若没有设置为NULL,就会产生野指针,它是指向垃圾内存的指针

linux c内存分配方式以及动态内存分配

从静态存储区分配(由系统控制)

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,staic变量

在栈上分配(由系统控制)

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。效率很高,凡事分配的内存容量有限

在堆上分配,亦称动态内存分配(由自己控制)

程序在运行时用malloc等函数申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生命周期有我们自己决定,使用非常灵活,但问题也最多

malloc函数

void *malloc(size_t size);

  • 使用:需要包含malloc.h头文件
  • 功能:允许从空闲内存池中分配连续内存但不初始化
  • 参数:size参数是一个所需字节
  • 返回:若分配成功则返回一个指向该内存块的指针,在使用时可根据需要做强制类型转换,否则返回NULL指针
calloc函数

void *calloc(size_t num_elements,size_t element_size);

  • 使用:需要包含malloc.h头文件
  • 功能:功能与malloc函数相同,但作初始化
  • 参数:num_elements是所需元素的数量,element_size是每个元素的字节
  • 返回:与malloc相同
realloc含糊

void *realloc(void *ptr,size_t new_size)

  • 使用:需要包含malloc.h头文件
  • 功能:在指针ptr指向的内存基础上扩大或缩小内存
  • 参数:ptr是指向先前通过malloccallocrealloc函数后分配的内存块的指针,new_size是内存块的新尺寸,可能大于或小于原有内存尺寸
  • 返回:若能调整内存大小则返回指向调整后内存的指针,否则返回NULL
realloc函数的使用注意点
  • 当扩展内存时,不会对添加的内存块的字节进行初始化
  • 弱不能调整内存则返回NULL,但原有内存中的数据不会发生改变
  • 若第一个参数为NULL那么功能等同于malloc函数,若第二个参数为0,那么会释放调用内存块。
  • 在缩小或扩大内存时一般不会对其进行移动,若无法扩大内存块(内存块后面的字节已经用于其他木的),那么可能会在别处分配心的内存块,然后把旧块的数据复制到新块中,并将旧块删除释放内存
free函数

void free(void *ptr);

  • 功能:释放由指针ptr指向的动态内存分配的内存块
free函数注意点
  • 如果ptrNULL指针,那么freeptr无论操作多少次都不会出问题
  • 如果ptr不是NULL指针,那么freeptr连续操作两次就会导致程序停止运行

示例

// stdlib会包含malloc
#include <stdlib.h>
#include <stdio.h>
void print_arr(int *p,int length);
int main(void)
{
        size_t length = 0;
        scanf("%d",&length);
        if(length > 0)
        {
                // 使用malloc分配内存
                //int *p = (int*)malloc(length);
                // 使用calloc分配内存
                int *p = (int*)calloc(sizeof(int),length);
                p[0] = 8;
                print_arr(p,length);
                // 使用 realloc修改申请的内存空间的大小
                p = realloc(p,length + 2);
                print_arr(p,length + 2);
                free(p);
                // 注意虽然释放了申请的内存,但是这个注意要清空下指针
                p = NULL;
        }
        return 0;
}
void print_arr(int *p,int length)
{
        for(int i = 0;i < length;i++)
        {
                printf("p[%d] = %d;\n",i,p[i]);
        }
}

gdb高级命令

工作环境相关命令

命令格式含义
set args 运行时的参数指定运行时的参数,如 set args 2
show args查看设置好的运行参数
path dir设定程序的运行路径
show paths查看程序的运行路径
set environment var[=value]设置环境变量
set var=value修改运行时变量的值
show environment [var]查看环境变量
cd dir进入到dir目录,相当于shell中的cd命令
pwd显示当前工作目录
shell command运行shell的command命令

设置断点和恢复命令

命令格式含义
info b查看所设置的断点
break(b) [行号|函数名] <条件表达式>设置断点
tbreak(b) [行号|函数名] <条件表达式>设置临时断点,到达后自动删除
delete [断点号]删除指定断点
disable [断点号]停止指定的断点名,使用info b仍然能查看此断点。同delete一样,省略断点号则停止所有断点
enable [断点号]激活指定断点,省略则激活所有断点号
condition [断点号] <条件表达式>修改对应断点的条件
ignore [断点号] <num>在程序执行时,忽略对应断点n次
step(s)单步恢复程序运行,且进入函数调用
next(n)单步恢复程序运行,但不会进入函数调用
until(u) 行号跳到指定行
finish运行程序,直到当前函数完成返回
continue(c)继续执行函数,直到函数结束或遇到新的断点

查看源码相关命令

命令格式含义
list(l) <行号>|><函数名>查看指定位置代码
file [文件名]加载指定文件
forward-search 正则表达式源代码前后搜索
reverse-search 正则表达式源代码向前搜索
show directories显示定义了的源文件搜索路径
info file显示加载到gdb内存中的代码
disassemble 函数名查看指定函数的反汇编代码

查看运行数据相关命令

命令格式含义
print(p) 表达式|变量查看程序运行时对应表达式和变量的值
x /<n/f/u> <addr>查看内存变量内容。其中n为整数表示显示内存的长度,f表示显示的格式(如:d表示十进制,x表示16进制,o表示八进制,t表示二进制),u表示从当前的地址往后请求显示的字节数,<addr>表示变量的内存的地址
display 表达式设定在单步运行或其他情况中,自动显示的对应表达式的内容(如:display /i $pc 显示c代码和汇编代码)

操作实例

添加删除断点
(gdb) b 5 # 在第五行加个断点
Breakpoint 1 at 0x400483: file src/gdb_test.c, line 5.
(gdb) info b # 查看所有的断点的信息
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400483 in main at src/gdb_test.c:5
(gdb) delete 1 # 根据断点的编号删除断点
(gdb) info b # 再次查看,可以发现断点已经没了
No breakpoints or watchpoints.
打印变量
(gdb) p a
$1 = 1
(gdb) p &a
$2 = (int *) 0x7fffffffe454
(gdb) x /d 0x7fffffffe454 # 十进制方式输出
0x7fffffffe454:    1
(gdb) x /x 0x7fffffffe454 # 十六进制输出
0x7fffffffe454:    0x00000001
(gdb) x /o 0x7fffffffe454 # 八进制输出
0x7fffffffe454:    01
(gdb) x /t 0x7fffffffe454 # 二进制输出
0x7fffffffe454:    00000000000000000000000000000001