分类 c 下的文章

c文件包含

文件包含时c预处理程序的另一个重要功能,被包含的文件名字必须要用双引号或一对尖括号括起来

文件包含的语法

#include <文件名>
#include "文件名"
  • 功能:一个源文件可将另一个源文件的内容全部包含进来,从而把指定的文件和当前的源程序文件连城一个源文件。
  • 处理过程:在预处理时,用被包含文件的内容替换该包含指令,再对包含后的文件作一个源文件编译
  • 一般而言,若调用标准库函数用#include <文件名>,若要包含用户自己编写的文件用#include "文件名"
  • 一个include指令只能指定一个被包含文件,允许潜逃包含。被包含的文件可以是源文件(.c)或者头文件(.h)

文件包含的搜索模式

#include <文件名>
若指定文件目录(如gcc -I选项指定的目录)则从此目录中找,否则按标准方式查找。
标准方式:从系统标准文件所在中寻找要包含的文件
#include "文件名"
先从存放c源文件的目录中查找,然后若用户指定目录(如gcc -I选项指定的目录),再从此目录中寻找要包含的文件,若找不到再按标准方式查找

文件包含的作用

  • 一个大的程序可以分为多个模块,由多个程序猿分别编程。有些公用的符号常量,结构体声明或宏定义等可单独组成一个文件,在其它文件的开头用包含指令包含该文件即可使用。
  • 可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错

多重包含

  • 同一个文件被多次包含称为多重包含
  • 多重包含可能会出现重复定义的编译错误
  • 为了防止多重包含可使用条件编译,注意条件编译只适用于一个文件中
#ifndef __HEADERNAME_H__
#define __HEADERNAME_H__ 1
#include "headername.h"
#endif

c条件编译

  • 一般情况下,源程序中所有的行都进行编译,但是有时希望对其中一部分内容在满足一定条件下才进行编译,也就是对一部分内容指定编译的条件,这就是条件编译。
  • 条件编译可以指定代码的一部分是被正常编译还是被完全忽略
  • 条件编译有利于提升程序的可移植性,增加程序的灵活性

条件编译语法一

#ifdef 标识符(宏名) //或#if defined(标识符)
      程序段1
#else
      程序段2
#endif

当所指定的宏已经被#define指令定义过,则在程序编译阶段编译程序段一,否则编译程序段二,#else可以没有

条件编译语法二

#ifdef 标识符(宏名) //或#if !defined(标识符)
      程序段1
#else
      程序段2
#endif

当宏未被#define指令定义过则编译程序段1,否则编译程序段2

条件编译语法三

#if 常量表达式
      程序段1
#else
      程序段2
#endif

或者

#if(常量表达式1)
      程序段1
#elif(常量表达式2)
      程序段2
#else
      程序段3
#endif

当指定的常量表达式值为真(非0)时就编译程序段1,否则编译程序段2

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,就会产生野指针,它是指向垃圾内存的指针