第 3 章 Linux 下 C 编 程 基 础 本 章 目 标 本 章 将 带 领 读 者 学 习 在 Linux 中 进 行 C 语 言 编 程 的 基 本 技 能 学 习 了 本 章 后, 读 者 能 够 掌 握 如 下 内 容 熟 悉 Linux 系 统 下 的 开 发 环 境 熟 悉 vi 的 基 本 操 作 熟 练 emacs 的 基 本 操 作 熟 悉 gcc 编 译 器 的 基 本 原 理 熟 练 使 用 gcc 编 译 器 的 常 用 选 项 熟 练 使 用 gdb 的 调 试 技 术 熟 悉 makefile 基 本 原 理 及 语 法 规 范 熟 练 使 用 autoconf 和 automake 生 成 makefile
3.1 Linux 下 C 语 言 编 程 概 述 3.1.1 C 语 言 简 单 回 顾 C 语 言 最 早 是 由 贝 尔 实 验 室 的 Dennis Ritchie 为 了 UNIX 的 辅 助 开 发 而 编 写 的, 它 是 在 B 语 言 的 基 础 上 开 发 出 来 的 尽 管 C 语 言 不 是 专 门 针 对 UNIX 操 作 系 统 或 机 器 编 写 的, 但 它 与 UNIX 系 统 的 关 系 十 分 紧 密 由 于 它 的 硬 件 无 关 性 和 可 移 植 性, 使 C 语 言 逐 渐 成 为 世 界 上 使 用 最 广 泛 的 计 算 机 语 言 为 了 进 一 步 规 范 C 语 言 的 硬 件 无 关 性,1987 年, 美 国 国 家 标 准 协 会 (ANSI) 根 据 C 语 言 问 世 以 来 各 种 版 本 对 C 语 言 的 发 展 和 扩 充, 制 定 了 新 的 标 准, 称 为 ANSI C ANSI C 语 言 比 原 来 的 标 准 C 语 言 有 了 很 大 的 发 展 目 前 流 行 的 C 语 言 编 译 系 统 都 是 以 它 为 基 础 的 C 语 言 的 成 功 并 不 是 偶 然 的, 它 强 大 的 功 能 和 它 的 可 移 植 性 让 它 能 在 各 种 硬 件 平 台 上 游 刃 自 如 总 体 而 言,C 语 言 有 如 下 特 点 (1)C 语 言 是 中 级 语 言 它 把 高 级 语 言 的 基 本 结 构 和 语 句 与 低 级 语 言 的 实 用 性 结 合 起 来 C 语 言 可 以 像 汇 编 语 言 一 样 对 位 字 节 和 地 址 进 行 操 作, 而 这 三 者 是 计 算 机 最 基 本 的 工 作 单 元 (2)C 语 言 是 结 构 化 的 语 言 C 语 言 采 用 代 码 及 数 据 分 隔, 使 程 序 的 各 个 部 分 除 了 必 要 的 信 息 交 流 外 彼 此 独 立 这 种 结 构 化 方 式 可 使 程 序 层 次 清 晰, 便 于 使 用 维 护 以 及 调 试 C 语 言 是 以 函 数 形 式 提 供 给 用 户 的, 这 些 函 数 可 方 便 地 调 用, 并 具 有 多 种 循 环 条 件 语 句 控 制 程 序 流 向, 从 而 使 程 序 完 全 结 构 化 (3)C 语 言 功 能 齐 全 C 语 言 具 有 各 种 各 样 的 数 据 类 型, 并 引 入 了 指 针 的 概 念, 可 使 程 序 效 率 更 高 另 外,C 语 言 也 具 有 强 大 的 图 形 功 能, 支 持 多 种 显 示 器 和 驱 动 器, 而 且 计 算 功 能 逻 辑 判 断 功 能 也 比 较 强 大, 可 以 实 现 决 策 目 的 (4)C 语 言 可 移 植 性 强 C 语 言 适 合 多 种 操 作 系 统, 如 DOS Windows Linux, 也 适 合 多 种 体 系 结 构, 因 此 尤 其 适 合 在 嵌 入 式 领 域 的 开 发 3.1.2 Linux 下 C 语 言 编 程 环 境 概 述 Linux 下 的 C 语 言 程 序 设 计 与 在 其 他 环 境 中 的 C 程 序 设 计 一 样, 主 要 涉 及 编 辑 器 编 译 链 接 器 调 试 器 及 项 目 管 理 工 具 现 在 我 们 先 对 这 4 种 工 具 进 行 简 单 介 绍, 后 面 会 对 其 一 一 进 行 讲 解 (1) 编 辑 器. Linux 下 的 编 辑 器 就 如 Windows 下 的 记 事 本 写 字 板 等 一 样, 完 成 对 所 录 入 文 字 的 编 辑 功 能 Linux 中 最 常 用 的 编 辑 器 有 vi(vim) 和 emacs, emacs 它 们 功 能 强 大 使 用 方 便, 深 受 编 程 爱 好 者 的 喜 爱 在 本 书 中, 着 重 介 绍 vi 和 (2) 编 译 链 接 器 编 译 是 指 源 代 码 转 化 生 成 可 执 行 代 码 的 过 程, 它 所 完 成 的 主 要 工 作 如 图 3.1 所 示
图 3.1 编 译 过 程 可 见, 编 译 过 程 是 非 常 复 杂 的, 它 包 括 词 法 语 法 和 语 义 的 分 析 中 间 代 码 的 生 成 和 优 化 符 号 表 的 管 理 和 出 错 处 理 等 在 Linux 中, 最 常 用 的 编 译 器 是 gcc 编 译 器 它 是 GNU 推 出 的 功 能 强 大 性 能 优 越 的 多 平 台 编 译 器, 其 执 行 效 率 与 一 般 的 编 译 器 相 比 平 均 效 率 要 高 20%~30% (3) 调 试 器 调 试 器 并 不 是 代 码 执 行 的 必 备 工 具, 而 是 专 为 方 便 程 序 员 调 试 程 序 而 用 的 有 编 程 经 验 的 读 者 都 知 道, 在 编 程 的 过 程 当 中, 往 往 调 试 所 消 耗 的 时 间 远 远 大 于 编 写 代 码 的 时 间 因 此, 有 一 个 功 能 强 大 使 用 方 便 的 调 试 器 是 必 不 可 少 的 gdb 是 绝 大 多 数 Linux 开 发 人 员 所 使 用 的 调 试 器, 它 可 以 方 便 地 设 置 断 点 单 步 跟 踪 等, 足 以 满 足 开 发 人 员 的 需 要 (4) 项 目 管 理 器 Linux 中 的 项 目 管 理 器 make 有 些 类 似 于 Windows 中 Visual c++ 里 的 工 程, 它 是 一 种 控 制 编 译 或 者 重 复 编 译 软 件 的 工 具, 另 外, 它 还 能 自 动 管 理 软 件 编 译 的 内 容 方 式 和 时 机, 使 程 序 员 能 够 把 精 力 集 中 在 代 码 的 编 写 上 而 不 是 在 源 代 码 的 组 织 上 3.2 常 用 编 辑 器 3.2.1 进 入 vi Linux 系 统 提 供 了 一 个 完 整 的 编 辑 器 家 族 系 列, 如 Ed Ex vi 和 emacs 等 按 功 能 它 们 可 以 分 为 两 大 类 : 行 编 辑 器 (Ed Ex) 和 全 屏 幕 编 辑 器 (vi emacs) 行 编 辑 器 每 次 只 能 对 一 行 进 行 操 作, 使 用 起 来 很 不 方 便 而 全 屏 幕 编 辑 器 可 以 对 整 个 屏 幕 进
行 编 辑, 用 户 编 辑 的 文 件 直 接 显 示 在 屏 幕 上, 从 而 克 服 了 行 编 辑 那 种 不 直 观 的 操 作 方 式, 便 于 用 户 学 习 和 使 用, 具 有 强 大 的 功 能 vi 是 Linux 系 统 的 第 一 个 全 屏 幕 交 互 式 编 辑 程 序, 它 从 诞 生 至 今 一 直 得 到 广 大 用 户 的 青 睐, 历 经 数 十 年 仍 然 是 人 们 主 要 使 用 的 文 本 编 辑 工 具, 足 以 见 其 生 命 力 之 强, 而 强 大 的 生 命 力 是 其 强 大 的 功 能 带 来 的 由 于 大 多 数 读 者 在 此 之 前 都 已 经 用 惯 了 Windows 平 台 上 的 编 辑 器, 因 此, 在 刚 刚 接 触 时 总 会 或 多 或 少 不 适 应, 但 只 要 习 惯 之 后, 就 能 感 受 到 它 的 方 便 与 快 捷 1.vi 的 模 式 vi 有 3 种 模 式, 分 别 为 命 令 行 模 式 插 入 模 式 及 命 令 行 模 式 下 面 具 体 介 绍 各 模 式 的 功 能 (1) 命 令 行 模 式 用 户 在 用 vi 编 辑 文 件 时, 最 初 进 入 的 为 一 般 模 式 在 该 模 式 中 用 户 可 以 通 过 上 下 移 动 光 标 进 行 删 除 字 符 或 整 行 删 除 等 操 作, 也 可 以 进 行 复 制 粘 贴 等 操 作, 但 无 法 编 辑 文 字 (2) 插 入 模 式 只 有 在 该 模 式 下, 用 户 才 能 进 行 文 字 编 辑 输 入, 用 户 按 [ESC] 可 键 回 到 命 令 行 模 式 (3) 底 行 模 式 在 该 模 式 下, 光 标 位 于 屏 幕 的 底 行 用 户 可 以 进 行 文 件 保 存 或 退 出 操 作, 也 可 以 设 置 编 辑 环 境, 如 寻 找 字 符 串 列 出 行 号 等 2.vi 的 基 本 流 程 (1) 进 入 vi, 即 在 命 令 行 下 键 入 vi hello ( 文 件 名 ) 此 时 进 入 的 是 命 令 行 模 式, 光 标 位 于 屏 幕 的 上 方, 如 图 3.2 所 示 图 3.2 进 入 vi 命 令 行 模 式
(2) 在 命 令 行 模 式 下 键 入 i 进 入 插 入 模 式, 如 图 3.3 所 示 可 以 看 出, 在 屏 幕 底 部 显 示 有 插 入 表 示 插 入 模 式 中 的 输 入 状 态, 在 该 模 式 下 可 以 输 入 文 字 信 息 图 3.3 进 入 vi 插 入 模 式 (3) 最 后, 在 插 入 模 式 中, 按 Esc 键, 则 当 前 模 式 转 入 命 令 行 模 式, 并 在 底 行 行 中 输 入 :wq ( 存 盘 退 出 ) 进 入 底 行 模 式, 如 图 3.4 所 示 这 样, 就 完 成 了 一 个 简 单 的 vi 操 作 流 程 : 命 令 行 模 式 插 入 模 式 底 行 模 式 由 于 vi 在 不 同 的 模 式 下 有 不 同 的 操 作 功 能, 因 此, 读 者 一 定 要 时 刻 注 意 屏 幕 最 下 方 的 提 示, 分 清 所 在 的 模 式
图 3.4 进 入 vi 底 行 模 式 3.vi 的 各 模 式 功 能 键 (1) 命 令 行 模 式 常 见 功 能 键 如 表 3.1 所 示 表 3.1 vi 命 令 行 模 式 功 能 键 功 能 键 功 能 i a o [ctrl]+[b] [ctrl]+[f] [ctrl]+[u] [ctrl]+[d] 切 换 到 插 入 模 式, 在 目 前 的 光 标 所 在 处 插 入 输 入 的 文 字, 已 存 在 的 文 字 会 向 后 退 切 换 到 插 入 模 式, 并 从 目 前 光 标 所 在 位 置 的 下 一 个 位 置 开 始 输 入 文 字 切 换 到 插 入 模 式, 且 从 行 首 开 始 插 入 新 的 一 行 屏 幕 往 后 翻 动 一 页 屏 幕 往 前 翻 动 一 页 屏 幕 往 后 翻 动 半 页 屏 幕 往 前 翻 动 半 页 0( 数 字 0) 光 标 移 到 本 行 的 开 头 G ng 光 标 移 动 到 文 件 的 最 后 光 标 移 动 到 第 n 行 $ 移 动 到 光 标 所 在 行 的 行 尾 n<enter> /name?name x X dd ndd yy nyy 光 标 向 下 移 动 n 行 在 光 标 之 后 查 找 一 个 名 为 name 的 字 符 串 在 光 标 之 前 查 找 一 个 名 为 name 的 字 符 串 删 除 光 标 所 在 位 置 的 一 个 字 符 删 除 光 标 所 在 位 置 的 前 一 个 字 符 删 除 光 标 所 在 行 从 光 标 所 在 行 开 始 向 下 删 除 n 行 复 制 光 标 所 在 行 复 制 光 标 所 在 行 开 始 的 向 下 n 行 p 将 缓 冲 区 内 的 字 符 粘 贴 到 光 标 所 在 位 置 ( 与 yy 搭 配 ) u 恢 复 前 一 个 动 作 (2) 插 入 模 式 的 功 能 键 只 有 一 个, 即 按 Esc 键 可 回 到 命 令 行 模 式 (3) 底 行 模 式 常 见 功 能 键 如 表 3.2 所 示 表 3.2 vi 底 行 模 式 功 能 键 功 能 键 功 能 :w 将 编 辑 的 文 件 保 存 到 磁 盘 中
:q 退 出 vi( 系 统 对 做 过 修 改 的 文 件 会 给 出 提 示 ) :q! 强 制 退 出 vi( 对 修 改 过 的 文 件 不 作 保 存 ) :wq 存 盘 后 退 出 :w [filename] 另 存 一 个 名 为 filename 的 文 件 :set nu :set nonu 显 示 行 号, 设 定 之 后, 会 在 每 一 行 的 前 面 显 示 对 应 行 号 取 消 行 号 显 示 注 意 vim 是 vi 的 升 级 版, 与 vi 相 比 扩 展 了 很 多 功 能 且 保 持 与 vi 的 90% 相 兼 容, 感 兴 趣 的 读 者 可 以 查 看 相 关 资 料 进 行 学 习 3.2.2 初 探 emacs 正 如 前 面 所 述,vi 是 一 款 功 能 非 常 强 大 的 编 辑 器, 它 能 够 方 便 快 捷 高 效 地 完 成 用 户 的 任 务, 那 么, 在 此 再 次 向 读 者 介 绍 另 一 款 编 辑 器 是 否 多 此 一 举 呢? 答 案 是 否 定 的 因 为 emacs 不 仅 仅 是 一 款 功 能 强 大 的 编 译 器, 而 且 是 一 款 融 合 编 辑 编 译 调 试 于 一 体 的 开 发 环 境 虽 然, 它 没 有 Visual Studio 一 样 绚 丽 的 界 面, 但 是 它 可 以 在 没 有 图 形 显 示 的 终 端 环 境 下 出 色 的 工 作, 相 信 追 求 强 大 功 能 和 工 作 效 率 的 用 户 不 会 介 意 它 朴 素 的 界 面 的 emacs 的 使 用 和 vi 截 然 不 同 在 emacs 里, 没 有 类 似 于 vi 的 3 种 模 式 emacs 只 有 一 种 模 式, 也 就 是 编 辑 模 式, 而 且 它 的 命 令 全 靠 功 能 键 完 成 因 此, 功 能 键 也 就 相 当 重 要 了 但 emacs 却 还 使 用 一 个 不 同 vi 的 模 式, 它 的 模 式 是 指 各 种 辅 助 环 境 比 如, 当 编 辑 普 通 文 本 时, 使 用 的 是 文 本 模 式 (Text Mode), 而 当 写 程 序 时, 使 用 的 则 是 如 c 模 式 shell 模 式 等 境 下 面, 首 先 介 绍 一 下 emacs 作 为 编 辑 器 的 使 用 方 法, 以 帮 助 读 者 熟 悉 emacs 的 环 注 意 emacs 缩 写 注 释 : C+<chr> 表 示 按 住 Ctrl 键 的 同 时 键 入 字 符 <chr> 因 此,C+f 就 表 示 按 住 Ctrl 键 同 时 键 入 f M+<chr> 表 示 当 键 入 字 符 <chr> 时 同 时 按 住 Meta 或 Edit 或 Alt 键 ( 通 常 为 Alt 键 ) 1.emacs 安 装 现 在 较 新 版 本 的 Linux( 如 本 书 中 所 用 的 Red Hat Enterprise 4 AS) 的 安 装 光 盘 中 一 般 都 自 带 有 emacs 的 安 装 包, 用 户 可 以 通 过 安 装 光 盘 进 行 安 装 ( 一 般 在 第 2 张 光 盘 中 ) 2. 启 动 emacs 安 装 完 emacs 之 后, 只 需 在 命 令 行 键 入 emacs [ 文 件 名 ] ( 若 缺 省 文 件 名, 也 可 在 emacs 编 辑 文 件 后 另 存 时 指 定 ), 也 可 从 编 程 emacs 打 开, 如 3.5 图 所 示 的 就 是 从 编 程 emacs 打 开 的 emacs 欢 迎 界 面
图 3.5 emacs 欢 迎 界 面 接 着 可 单 击 任 意 键 进 入 emacs 的 工 作 窗 口, 如 图 3.6 所 示 从 图 中 可 见,emacs 的 工 作 窗 口 分 为 上 下 两 个 部 分, 上 部 为 编 辑 窗 口, 底 部 为 命 令 显 示 窗 口, 用 户 执 行 功 能 键 的 功 能 都 会 在 底 部 有 相 应 的 显 示, 有 时 也 需 要 用 户 在 底 部 窗 口 输 入 相 应 的 命 令, 如 查 找 字 符 串 等 图 3.6 emacs 的 工 作 窗 口
3. 进 入 emacs 在 进 入 emacs 后, 即 可 进 行 文 件 的 编 辑 由 于 emacs 只 有 一 种 编 辑 模 式, 因 此 用 户 无 需 进 行 模 式 间 的 切 换 下 面 介 绍 emacs 中 基 本 编 辑 功 能 键 (1) 移 动 光 标 虽 然 在 emacs 中 可 以 使 用 上 下 左 右 方 向 键 来 移 动 单 个 字 符, 但 笔 者 还 是 建 议 读 者 学 习 其 对 应 功 能 键, 因 为 它 们 不 仅 能 在 所 有 类 型 的 终 端 上 工 作, 而 且 读 者 将 会 发 现 在 熟 练 使 用 之 后, 输 入 这 些 Ctrl 加 字 符 会 比 按 方 向 键 快 很 多 表 3.3 列 举 了 emacs 中 光 标 移 动 的 常 见 功 能 键 表 3.3 emacs 光 标 移 动 功 能 键 功 能 键 功 能 功 能 键 功 能 C-f 向 前 移 动 一 个 字 符 M-b 向 后 移 动 一 个 单 词 C-b 向 后 移 动 一 个 字 符 C-a 移 动 到 行 首 C-p 移 动 到 上 一 行 C-e 移 动 到 行 尾 C-n 移 动 到 下 一 行 M-<(M 加 小 于 号 ) 移 动 光 标 到 整 个 文 本 的 开 头 M-f 向 前 移 动 一 个 单 词 M->(M 加 大 于 号 ) 移 动 光 标 到 整 个 文 本 的 末 尾 (2) 剪 切 和 粘 贴 在 emacs 中 可 以 使 用 Delete 和 BackSpace 删 除 光 标 前 后 的 字 符, 这 和 用 户 之 前 的 习 惯 一 致, 在 此 就 不 赘 述 以 词 和 行 为 单 位 的 剪 切 和 粘 贴 功 能 键 如 表 3.4 所 示 表 3.4 emacs 剪 切 和 粘 贴 功 能 键 功 能 功 能 键 功 能 M-Delete 剪 切 光 标 前 面 的 单 词 M-k 剪 切 从 光 标 位 置 到 句 尾 的 内 容 M-d 剪 切 光 标 前 面 的 单 词 C-y 将 缓 冲 区 中 的 内 容 粘 贴 到 光 标 所 在 的 位 置 C-k 剪 切 从 光 标 位 置 到 行 尾 的 内 容 C-x u 撤 销 操 作 ( 先 操 作 C+x, 接 着 再 单 击 u) 注 意 在 emacs 中 对 单 个 字 符 的 操 作 是 删 除, 而 对 词 和 句 的 操 作 是 剪 切, 即 保 存 在 缓 冲 区 中, 以 备 后 面 的 粘 贴 所 用 (3) 复 制 文 本 在 emacs 中 的 复 制 文 本 包 括 两 步 : 选 择 复 制 区 域 和 粘 贴 文 本 选 择 复 制 区 域 的 方 法 是 : 首 先 在 复 制 起 始 点 (A) 按 下 C-Space 或 C-@(C-Shift-2) 使 它 成 为 一 个 标 识 点, 再 将 光 标 移 至 复 制 结 束 点 ( B ), 再 按 下 M-w, 就 可 将 A 与 B 之 间 的 文 本 复 制 到 系 统 的 缓 冲 区 中 再 使 用 功 能 键 C-y 将 其 粘 贴 到 指 定 位 置 (4) 查 找 文 本
查 找 文 本 的 功 能 键 如 表 3.5 所 示 表 3.5 emacs 查 找 文 本 功 能 键 功 能 键 功 能 C-s C-r 查 找 光 标 以 后 的 内 容, 并 在 对 话 框 的 I-search: 后 输 入 要 查 找 的 字 符 串 查 找 光 标 以 前 的 内 容, 并 在 对 话 框 的 I-search backward: 后 输 入 要 查 找 的 字 符 串 (5) 保 存 文 档 在 emacs 中 保 存 文 档 的 功 能 键 为 C+x C+s ( 即 先 操 作 C+x, 接 着 再 操 作 C+s), 这 时, 屏 幕 底 下 的 对 话 框 会 出 现 如 Wrote /root/workplace/editor/why 的 字 样, 如 图 3.7 所 示 图 3.7 emacs 中 保 存 文 档 另 外,emacs 在 编 辑 时 会 为 每 个 文 件 提 供 自 动 保 存 (auto save) 的 机 制, 而 且 自 动 保 存 的 文 件 的 文 件 名 前 后 都 有 一 个 #, 例 如, 编 辑 名 为 hello.c 的 文 件, 其 自 动 保 存 的 文 件 的 文 件 名 就 叫 #hello.c# 当 用 户 正 常 地 保 存 了 文 件 后,emacs 就 会 删 除 这 个 自 动 保 存 的 文 件 这 个 机 制 当 系 统 发 生 异 常 时 非 常 有 用 (6) 退 出 文 档 在 emacs 中 退 出 文 档 的 功 能 键 为 C-x C-c 4.emacs 中 的 模 式 emacs 不 仅 仅 是 个 强 大 的 编 译 器, 它 还 是 一 个 集 编 译 调 试 等 于 一 体 的 工 作 环 境 在 这 里, 读 者 将 会 了 解 到 emacs 作 为 编 译 器 的 最 基 本 的 概 念, 感 兴 趣 的 读 者 可 以 参 考 Learning GNU Emacs,Second Edition 一 书 进 一 步 学 习 emacs
在 emacs 中 并 没 有 像 vi 中 那 样 的 命 令 行 编 辑 模 式, 只 有 一 种 编 辑 模 式 这 里 所 说 的 模 式 是 指 emacs 里 的 各 种 辅 助 环 境 下 面 着 重 讲 解 C 模 式 当 我 们 启 动 某 一 文 件 时,emacs 会 判 断 文 件 的 类 型, 从 而 自 动 选 择 相 应 的 模 式 当 然, 用 户 也 可 以 手 动 启 动 各 种 模 式, 用 功 能 键 M-x, 然 后 再 输 入 模 式 的 名 称, 如 图 3.8 所 示, 这 样 就 启 动 了 C 模 式 图 3.8 emacs 中 选 择 模 式 在 强 大 的 C 模 式 下, 用 户 拥 有 自 动 缩 进 注 释 预 处 理 扩 展 自 动 状 态 等 强 大 功 能 在 C 模 式 下 编 辑 代 码 时, 可 以 用 Tab 键 自 动 地 将 当 前 行 的 代 码 产 生 适 当 的 缩 进, 使 代 码 结 构 清 晰 美 观, 它 也 可 以 指 定 缩 进 的 规 则 源 代 码 要 有 良 好 的 可 读 性, 必 须 要 有 良 好 的 注 释 在 emacs 中, 用 M- 可 以 产 生 一 条 右 缩 进 的 注 释 C 模 式 下 是 /* comments */ 形 式 的 注 释,C++ 模 式 下 是 // comments 形 式 的 注 释 当 用 户 高 亮 选 定 某 段 文 本, 然 后 操 作 C-c C-c, 就 可 以 注 释 该 段 文 字 emacs 还 可 以 使 用 C 预 处 理 其 运 行 代 码 的 一 部 分, 以 便 让 程 序 员 检 测 宏 条 件 编 译 以 及 include 语 句 的 效 果 5.emacs 编 译 调 试 程 序 emacs 可 以 让 程 序 员 在 emacs 环 境 里 编 译 自 己 的 软 件 此 时, 编 辑 器 把 编 译 器 的 输 出 和 程 序 代 码 连 接 起 来 程 序 员 可 以 像 使 用 Windows 的 其 他 开 发 工 具 一 样, 将 出 错 位 置 和 代 码 定 位 联 系 起 来 emacs 默 认 的 编 辑 命 令 是 对 一 个 make( 在 本 章 3.6 节 中 会 详 细 介 绍 ) 的 调 用 用 户 可 以 打 开 tool 下 的 Compile 进 行 查 看 emacs 可 以 支 持 大 量 的 工 程 项 目, 以 方 便 程 序 员 的 开 发 另 外,emacs 为 gdb 调 试 器 提 供 了 一 个 功 能 齐 全 的 接 口 在 emacs 中 使 用 gdb 的 时 候, 程 序 员 不 仅 能 够 获 得 gdb 的 全 部 标 准 特 性, 还 可 以 获 得 通 过 接 口 增 强 而 产 生 的
其 他 性 能 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 3.3 gcc 编 译 器 GNU CC( 简 称 为 gcc) 是 GNU 项 目 中 符 合 ANSI C 标 准 的 编 译 系 统, 能 够 编 译 用 C C++ 和 Object C 等 语 言 编 写 的 程 序 gcc 不 仅 功 能 强 大, 而 且 可 以 编 译 如 C C++ Object C Java Fortran Pascal Modula-3 和 Ada 等 多 种 语 言, 而 且 gcc 是 一 个 交 叉 平 台 编 译 器, 它 能 够 在 当 前 CPU 平 台 上 为 多 种 不 同 体 系 结 构 的 硬 件 平 台 开 发 软 件, 因 此 尤 其 适 合 在 嵌 入 式 领 域 的 开 发 编 译 本 章 中 的 示 例, 除 非 特 别 注 明, 否 则 均 采 用 4.x.x 的 gcc 版 本 表 3.6 所 示 为 gcc 支 持 编 译 源 文 件 的 后 缀 及 其 解 释 表 3.6 gcc 所 支 持 后 缀 名 解 释 后 缀 名 所 对 应 的 语 言 后 缀 名 所 对 应 的 语 言.c C 原 始 程 序.s/.S 汇 编 语 言 原 始 程 序.C/.cc/.cxx C++ 原 始 程 序.h 预 处 理 文 件 ( 头 文 件 ).m Objective-C 原 始 程 序.o 目 标 文 件.i 已 经 过 预 处 理 的 C 原 始 程 序.a/.so 编 译 后 的 库 文 件.ii 已 经 过 预 处 理 的 C++ 原 始 程 序 3.3.1 gcc 编 译 流 程 解 析 如 本 章 开 头 提 到 的,gcc 的 编 译 流 程 分 为 了 4 个 步 骤, 分 别 为 : 预 处 理 (Pre-Processing); 编 译 (Compiling); 汇 编 (Assembling); 链 接 (Linking) 下 面 就 具 体 来 查 看 一 下 gcc 是 如 何 完 成 以 上 4 个 步 骤 的 首 先 看 一 下 hello.c 的 源 代 码 : #include <stdio.h> int main() { } printf("hello! This is our embedded world!\n"); return 0; (1) 预 处 理 阶 段 在 该 阶 段, 对 包 含 的 头 文 件 (#include) 和 宏 定 义 (#define #ifdef 等 ) 进 行 处 理 在 上 述 代 码 的 预 处 理 过 程 中, 编 译 器 将 包 含 的 头 文 件 stdio.h 编 译 进 来, 并 且 用 户 可 以 使 用 gcc 的 选 项 -E 进 行 查 看, 该 选 项 的 作 用 是 让 gcc 在 预 处 理 结 束 后 停 止 编 译 过
程 注 意 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 gcc 指 令 的 一 般 格 式 为 :gcc [ 选 项 ] 要 编 译 的 文 件 [ 选 项 ] [ 目 标 文 件 ] 其 中, 目 标 文 件 可 缺 省,gcc 默 认 生 成 可 执 行 的 文 件, 名 为 : 编 译 文 件.out [root@localhost gcc]# gcc E hello.c o hello.i 在 此 处, 选 项 -o 是 指 目 标 文 件, 由 表 3.6 可 知,.i 文 件 为 已 经 过 预 处 理 的 C 程 序 以 下 列 出 了 hello.i 文 件 的 部 分 内 容 : typedef int (* gconv_trans_fct) (struct gconv_step *, struct gconv_step_data *, void *, const unsigned char *, const unsigned char **, const unsigned char *, unsigned char **, size_t *); # 2 "hello.c" 2 int main() { } printf("hello! This is our embedded world!\n"); return 0; 由 此 可 见,gcc 确 实 进 行 了 预 处 理, 它 把 stdio.h 的 内 容 插 入 hello.i 文 件 中 (2) 编 译 阶 段 接 下 来 进 行 的 是 编 译 阶 段, 在 这 个 阶 段 中,gcc 首 先 要 检 查 代 码 的 规 范 性 是 否 有 语 法 错 误 等, 以 确 定 代 码 实 际 要 做 的 工 作, 在 检 查 无 误 后,gcc 把 代 码 翻 译 成 汇 编 语 言 用 户 可 以 使 用 -S 选 项 来 进 行 查 看, 该 选 项 只 进 行 编 译 而 不 进 行 汇 编, 结 果 生 成 汇 编 代 码 [root@localhost gcc]# gcc S hello.i o hello.s 以 下 列 出 了 hello.s 的 内 容, 可 见 gcc 已 经 将 其 转 化 为 汇 编 代 码 了, 感 兴 趣 的 读 者 可 以 分 析 一 下 这 一 个 简 单 的 C 语 言 小 程 序 是 如 何 用 汇 编 代 码 实 现 的.LC0:.file "hello.c".section.rodata.align 4.string.text.globl main.type main, @function main: pushl %ebp movl %esp, %ebp "Hello! This is our embedded world!"
subl $8, %esp andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp subl $12, %esp pushl $.LC0 call puts addl $16, %esp movl $0, %eax leave ret.size main,.-main.ident "GCC: (GNU) 4.0.0 200XYZ19 (Red Hat 4.0.0-8)".section.note.GNU-stack,"",@progbits (3) 汇 编 阶 段 汇 编 阶 段 是 把 编 译 阶 段 生 成 的.s 文 件 转 成 目 标 文 件, 读 者 在 此 使 用 选 项 -c 就 可 看 到 汇 编 代 码 已 转 化 为.o 的 二 进 制 目 标 代 码 了, 如 下 所 示 : [root@localhost gcc]# gcc c hello.s o hello.o (4) 链 接 阶 段 在 成 功 编 译 之 后, 就 进 入 了 链 接 阶 段 这 里 涉 及 一 个 重 要 的 概 念 : 函 数 库 读 者 可 以 重 新 查 看 这 个 小 程 序, 在 这 个 程 序 中 并 没 有 定 义 printf 的 函 数 实 现, 且 在 预 编 译 中 包 含 进 的 stdio.h 中 也 只 有 该 函 数 的 声 明, 而 没 有 定 义 函 数 的 实 现, 那 么, 是 在 哪 里 实 现 printf 函 数 的 呢? 最 后 的 答 案 是 : 系 统 把 这 些 函 数 的 实 现 都 放 到 名 为 libc.so.6 的 库 文 件 中 去 了, 在 没 有 特 别 指 定 时,gcc 会 到 系 统 默 认 的 搜 索 路 径 /usr/lib 下 进 行 查 找, 也 就 是 链 接 到 libc.so.6 函 数 库 中 去, 这 样 就 能 调 用 函 数 printf 了, 而 这 也 正 是 链 接 的 作 用 函 数 库 有 静 态 库 和 动 态 库 两 种 静 态 库 是 指 编 译 链 接 时, 将 库 文 件 的 代 码 全 部 加 入 可 执 行 文 件 中, 因 此 生 成 的 文 件 比 较 大, 但 在 运 行 时 也 就 不 再 需 要 库 文 件 了 其 后 缀 名 通 常 为.a 动 态 库 与 之 相 反, 在 编 译 链 接 时 并 没 有 将 库 文 件 的 代 码 加 入 可 执 行 文 件 中, 而 是 在 程 序 执 行 时 加 载 库, 这 样 可 以 节 省 系 统 的 开 销 一 般 动 态 库 的 后 缀 名 为.so, 如 前 面 所 述 的 libc.so.6 就 是 动 态 库 gcc 在 编 译 时 默 认 使 用 动 态 库 完 成 了 链 接 之 后,gcc 就 可 以 生 成 可 执 行 文 件, 如 下 所 示 [root@localhost gcc]# gcc hello.o o hello 运 行 该 可 执 行 文 件, 出 现 的 正 确 结 果 如 下
[root@localhost gcc]#./hello Hello! This is our embedded world! 3.3.2 gcc 编 译 选 项 分 析 gcc 有 超 过 100 个 可 用 选 项, 主 要 包 括 总 体 选 项 告 警 和 出 错 选 项 优 化 选 项 和 体 系 结 构 相 关 选 项 以 下 对 每 一 类 中 最 常 用 的 选 项 进 行 讲 解 (1) 常 用 选 项 gcc 的 常 用 选 项 如 表 3.7 所 示, 很 多 在 前 面 的 示 例 中 已 经 有 所 涉 及 表 3.7 gcc 常 用 选 项 列 表 选 项 含 义 -c 只 编 译 不 链 接, 生 成 目 标 文 件.o -S 只 编 译 不 汇 编, 生 成 汇 编 代 码 -E 只 进 行 预 编 译, 不 做 其 他 处 理 -g 在 可 执 行 程 序 中 包 含 标 准 调 试 信 息 -o file 将 file 文 件 指 定 为 输 出 文 件 -v 打 印 出 编 译 器 内 部 编 译 各 过 程 的 命 令 行 信 息 和 编 译 器 的 版 本 -I dir 在 头 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录 前 一 小 节 已 经 讲 解 了 -c -E -o -S 选 项 的 使 用 方 法, 在 此 主 要 讲 解 另 外 2 个 非 常 常 用 的 库 依 赖 选 项 -I dir -I dir 正 如 上 表 中 所 述, -I dir 选 项 可 以 在 头 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录 由 于 Linux 中 头 文 件 都 默 认 放 到 了 /usr/include/ 目 录 下, 因 此, 当 用 户 希 望 添 加 放 置 在 其 他 位 置 的 头 文 件 时, 就 可 以 通 过 -I dir 选 项 来 指 定, 这 样,gcc 就 会 到 相 应 的 位 置 查 找 对 应 的 目 录 比 如 在 /root/workplace/gcc 下 有 两 个 文 件 : /* hello1.c */ #include<my.h> int main() { } printf("hello!!\n"); return 0; /* my.h */ #include<stdio.h> 这 样, 就 可 在 gcc 命 令 行 中 加 入 -I 选 项 : [root@localhost gcc] gcc hello1.c I /root/workplace/gcc/ -o hello1 这 样,gcc 就 能 够 执 行 出 正 确 结 果
小 知 识 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 在 include 语 句 中, <> 表 示 在 标 准 路 径 中 搜 索 头 文 件, 表 示 在 本 目 录 中 搜 索 故 在 上 例 中, 可 把 hello1.c 的 #include<my.h> 改 为 #include my.h 就 不 需 要 加 上 -I 选 项 了 (2) 库 选 项 gcc 库 选 项 如 表 3.8 所 示 表 3.8 gcc 库 选 项 列 表 选 项 含 义 -static -shared 进 行 静 态 编 译, 即 链 接 静 态 库, 禁 止 使 用 动 态 库 1. 可 以 生 成 动 态 库 文 件 2. 进 行 动 态 编 译, 尽 可 能 地 链 接 动 态 库, 只 有 当 没 有 动 态 库 时 才 会 链 接 同 名 的 静 态 库 ( 默 认 选 项, 即 可 省 略 ) -L dir 在 库 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录 -lname -fpic( 或 -fpic) 链 接 称 为 libname.a( 静 态 库 ) 或 者 libname.so( 动 态 库 ) 的 库 文 件 若 两 个 库 都 存 在 则 根 据 编 译 方 式 (-static 还 是 -shared) 而 进 行 链 接 生 成 使 用 相 对 地 址 的 位 置 无 关 的 目 标 代 码 (Position Independent Code) 然 后 通 常 使 用 gcc 的 -static 选 项 从 该 PIC 目 标 文 件 生 成 动 态 库 文 件 我 们 通 常 需 要 将 一 些 常 用 的 公 共 函 数 编 译 并 集 成 到 二 进 制 文 件 (Linux 的 ELF 格 式 文 件 ), 以 便 其 他 程 序 可 重 复 地 使 用 该 文 件 中 的 函 数, 此 时 将 这 种 文 件 叫 做 函 数 库, 使 用 函 数 库 不 仅 能 够 节 省 很 多 内 存 和 存 储 器 的 空 间 资 源, 而 且 更 重 要 的 是 大 大 降 低 开 发 难 度 和 开 销, 提 高 开 发 效 率 并 增 强 程 序 的 结 构 性 实 际 上, 在 Linux 中 的 每 个 程 序 都 会 链 接 到 一 个 或 者 多 个 库 比 如 使 用 C 函 数 的 程 序 会 链 接 到 C 运 行 时 库,Qt 应 用 程 序 会 链 接 到 Qt 支 持 的 相 关 图 形 库 等 函 数 库 有 静 态 库 和 动 态 库 两 种, 静 态 库 是 一 系 列 的 目 标 文 件 (.o 文 件 ) 的 归 档 文 件 ( 文 件 名 格 式 为 libname.a), 如 果 在 编 译 某 个 程 序 时 链 接 静 态 库, 则 链 接 器 将 会 搜 索 静 态 库, 从 中 提 取 出 它 所 需 要 的 目 标 文 件 并 直 接 复 制 到 该 程 序 的 可 执 行 二 进 制 文 件 (ELF 格 式 文 件 ) 之 中 ; 动 态 库 ( 文 件 名 格 式 为 libname.so[. 主 版 本 号. 次 版 本 号. 发 行 号 ]) 在 程 序 编 译 时 并 不 会 被 链 接 到 目 标 代 码 中, 而 是 在 程 序 运 行 时 才 被 载 入 下 面 举 一 个 简 单 的 例 子, 讲 解 如 何 怎 么 创 建 和 使 用 这 两 种 函 数 库 首 先 创 建 unsgn_pow.c 文 件, 它 包 含 unsgn_pow() 函 数 的 定 义, 具 体 代 码 如 下 所 示 /* unsgn_pow.c: 库 程 序 */ unsigned long long unsgn_pow(unsigned int x, unsigned int y) { unsigned long long res = 1; if (y == 0) { res = 1; } else if (y == 1)
} 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 { res = x; } else { res = x * unsgn_pow(x, y - 1); } return res; 然 后 创 建 pow_test.c 文 件, 它 会 调 用 unsgn_pow() 函 数 /* pow_test.c */ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { unsigned int x, y; unsigned long long res; if ((argc < 3) (sscanf(argv[1], "%u", &x)!= 1) (sscanf(argv[2], "%u", &y))!= 1) { printf("usage: pow base exponent\n"); exit(1); } res = unsgn_pow(x, y); printf("%u ^ %u = %u\n", x, y, res); exit(0); } 我 们 用 unsgn_pow.c 文 件 可 以 制 作 一 个 函 数 库 下 面 分 别 讲 解 怎 么 生 成 静 态 库 和 动 态 库 静 态 库 的 创 建 和 使 用 创 建 静 态 库 比 较 简 单, 使 用 归 档 工 具 ar 将 一 些 目 标 文 件 集 成 在 一 起 [root@localhost lib]# gcc -c unsgn_pow.c [root@localhost lib]# ar rcsv libpow.a unsgn_pow.o a - unsgn_pow.o 下 面 编 译 主 程 序, 它 将 会 链 接 到 刚 生 成 的 静 态 库 libpow.a 具 体 运 行 结 果 如 下 所 示 [root@localhost lib]# gcc -o pow_test pow_test.c -L. lpow
[root@localhost lib]#./pow_test 2 10 2 ^ 10 = 1024 其 中, 选 项 -L dir 的 功 能 与 -I dir 类 似, 能 够 在 库 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录, 而 -lname 选 项 指 示 编 译 时 链 接 到 库 文 件 libname.a 或 者 libname.so 本 实 例 中, 程 序 pow_test.c 需 要 使 用 当 前 目 录 下 的 一 个 静 态 库 libpow.a 动 态 库 的 创 建 和 使 用 首 先 使 用 gcc 的 -fpic 选 项 为 动 态 库 构 造 一 个 目 标 文 件 [root@localhost lib]# gcc -fpic -Wall -c unsgn_pow.c 接 下 来, 使 用 -shared 选 项 和 已 创 建 的 位 置 无 关 目 标 代 码, 生 成 一 个 动 态 库 libpow.so [root@localhost lib]# gcc -shared -o libpow.so unsgn_pow.o 下 面 编 译 主 程 序, 它 将 会 链 接 到 刚 生 成 的 动 态 库 libpow.so [root@localhost lib]# gcc -o pow_test pow_test.c -L. lpow 在 运 行 可 执 行 程 序 之 前, 需 要 注 册 动 态 库 的 路 径 名 其 方 法 有 几 种 : 修 改 /etc/ld.so.conf 文 件, 或 者 修 改 LD_LIBRARY_PATH 环 境 变 量, 或 者 将 库 文 件 直 接 复 制 到 /lib 或 者 /usr/lib 目 录 下 ( 这 两 个 目 录 为 系 统 的 默 认 的 库 路 径 名 ) [root@localhost lib]# cp libpow.so /lib [root@localhost lib]#./pow_test 2 10 2 ^ 10 = 1024 动 态 库 只 有 当 使 用 它 的 程 序 执 行 时 才 被 链 接 使 用, 而 不 是 将 需 要 的 部 分 直 接 编 译 入 可 执 行 文 件 中, 并 且 一 个 动 态 库 可 以 被 多 个 程 序 使 用 故 可 称 为 共 享 库, 而 静 态 库 将 会 整 合 到 程 序 中, 因 此 在 程 序 执 行 时 不 用 加 载 静 态 库 从 而 可 知, 链 接 到 静 态 库 会 使 用 户 的 程 序 臃 肿, 并 且 难 以 升 级, 但 是 可 能 会 比 较 容 易 部 署 而 链 接 到 动 态 库 会 使 用 户 的 程 序 轻 便, 并 且 易 于 升 级, 但 是 会 难 以 部 署 (3) 告 警 和 出 错 选 项 gcc 的 告 警 和 出 错 选 项 如 表 3.9 所 示 表 3.9 gcc 警 告 和 出 错 选 项 选 项 列 表 选 项 含 义 -ansi -pedantic -pedantic-error 支 持 符 合 ANSI 标 准 的 C 程 序 允 许 发 出 ANSI C 标 准 所 列 的 全 部 警 告 信 息 允 许 发 出 ANSI C 标 准 所 列 的 全 部 错 误 信 息 -w 关 闭 所 有 告 警 -Wall -werror 允 许 发 出 gcc 提 供 的 所 有 有 用 的 报 警 信 息 把 所 有 的 告 警 信 息 转 化 为 错 误 信 息, 并 在 告 警 发 生 时 终 止 编 译 过 程 下 面 结 合 实 例 对 这 几 个 告 警 和 出 错 选 项 进 行 简 单 的 讲 解 有 以 下 程 序 段 :
#include<stdio.h> 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 void main() { } long long tmp = 1; printf("this is a bad code!\n"); return 0; 这 是 一 个 很 糟 糕 的 程 序, 读 者 可 以 考 虑 一 下 有 哪 些 问 题 -ansi 该 选 项 强 制 gcc 生 成 标 准 语 法 所 要 求 的 告 警 信 息, 尽 管 这 还 并 不 能 保 证 所 有 没 有 警 告 的 程 序 都 是 符 合 ANSI C 标 准 的 运 行 结 果 如 下 所 示 : [root@localhost gcc]# gcc ansi warning.c o warning warning.c: 在 函 数 main 中 : warning.c:7 警 告 : 在 无 返 回 值 的 函 数 中, return 带 返 回 值 warning.c:4 警 告 : main 的 返 回 类 型 不 是 int 可 以 看 出, 该 选 项 并 没 有 发 现 long long 这 个 无 效 数 据 类 型 的 错 误 -pedantic 打 印 ANSI C 标 准 所 列 出 的 全 部 警 告 信 息, 同 样 也 保 证 所 有 没 有 警 告 的 程 序 都 是 符 合 ANSI C 标 准 的 其 运 行 结 果 如 下 所 示 : 误 [root@localhost gcc]# gcc pedantic warning.c o warning warning.c: 在 函 数 main 中 : warning.c:5 警 告 :ISO C90 不 支 持 long long warning.c:7 警 告 : 在 无 返 回 值 的 函 数 中, return 带 返 回 值 warning.c:4 警 告 : main 的 返 回 类 型 不 是 int 可 以 看 出, 使 用 该 选 项 查 出 了 long long 这 个 无 效 数 据 类 型 的 错 误 -Wall 打 印 gcc 能 够 提 供 的 所 有 有 用 的 报 警 信 息 该 选 项 的 运 行 结 果 如 下 所 示 : [root@localhost gcc]# gcc Wall warning.c o warning warning.c:4 警 告 : main 的 返 回 类 型 不 是 int warning.c: 在 函 数 main 中 : warning.c:7 警 告 : 在 无 返 回 值 的 函 数 中, return 带 返 回 值 warning.c:5 警 告 : 未 使 用 的 变 量 tmp 使 用 -Wall 选 项 找 出 了 未 使 用 的 变 量 tmp, 但 它 并 没 有 找 出 无 效 数 据 类 型 的 错 另 外,gcc 还 可 以 利 用 选 项 对 单 独 的 常 见 错 误 分 别 指 定 警 告 (4) 优 化 选 项 gcc 可 以 对 代 码 进 行 优 化, 它 通 过 编 译 选 项 -On 来 控 制 优 化 代 码 的 生 成, 其 中
n 是 一 个 代 表 优 化 级 别 的 整 数 对 于 不 同 版 本 的 gcc 来 讲,n 的 取 值 范 围 及 其 对 应 的 优 化 效 果 可 能 并 不 完 全 相 同, 比 较 典 型 的 范 围 是 从 0 变 化 到 2 或 3 不 同 的 优 化 级 别 对 应 不 同 的 优 化 处 理 工 作 如 使 用 优 化 选 项 -O 主 要 进 行 线 程 跳 转 (Thread Jump) 和 延 迟 退 栈 (Deferred Stack Pops) 两 种 优 化 使 用 优 化 选 项 -O2 除 了 完 成 所 有 -O1 级 别 的 优 化 之 外, 同 时 还 要 进 行 一 些 额 外 的 调 整 工 作, 如 处 理 器 指 令 调 度 等 选 项 -O3 则 还 包 括 循 环 展 开 和 其 他 一 些 与 处 理 器 特 性 相 关 的 优 化 工 作 虽 然 优 化 选 项 可 以 加 速 代 码 的 运 行 速 度, 但 对 于 调 试 而 言 将 是 一 个 很 大 的 挑 战 因 为 代 码 在 经 过 优 化 之 后, 原 先 在 源 程 序 中 声 明 和 使 用 的 变 量 很 可 能 不 再 使 用, 控 制 流 也 可 能 会 突 然 跳 转 到 意 外 的 地 方, 循 环 语 句 也 有 可 能 因 为 循 环 展 开 而 变 得 到 处 都 有, 所 有 这 些 对 调 试 来 讲 都 将 是 一 场 噩 梦 所 以 笔 者 建 议 在 调 试 的 时 候 最 好 不 使 用 任 何 优 化 选 项, 只 有 当 程 序 在 最 终 发 行 的 时 候 才 考 虑 对 其 进 行 优 化 (5) 体 系 结 构 相 关 选 项 gcc 的 体 系 结 构 相 关 选 项 如 表 3.10 所 示 表 3.10 gcc 体 系 结 构 相 关 选 项 列 表 选 项 含 义 -mcpu=type -mieee-fp -mno-ieee-fp -msoft-float -mshort -mrtd 针 对 不 同 的 CPU 使 用 相 应 的 CPU 指 令 可 选 择 的 type 有 i386 i486 pentium 及 i686 使 用 IEEE 标 准 进 行 浮 点 数 的 比 较 不 使 用 IEEE 标 准 进 行 浮 点 数 的 比 较 输 出 包 含 浮 点 库 调 用 的 目 标 代 码 把 int 类 型 作 为 16 位 处 理, 相 当 于 short int 强 行 将 函 数 参 数 个 数 固 定 的 函 数 用 ret NUM 返 回, 节 省 调 用 函 数 的 一 条 指 令 这 些 体 系 结 构 相 关 选 项 在 嵌 入 式 的 设 计 中 会 有 较 多 的 应 用, 读 者 需 根 据 不 同 体 系 结 构 将 对 应 的 选 项 进 行 组 合 处 理 在 本 书 后 面 涉 及 具 体 实 例 时 将 会 有 针 对 性 的 讲 解 3.4 gdb 调 试 器 调 试 是 所 有 程 序 员 都 会 面 临 的 问 题 如 何 提 高 程 序 员 的 调 试 效 率, 更 好 更 快 地 定 位 程 序 中 的 问 题 从 而 加 快 程 序 开 发 的 进 度, 是 大 家 都 很 关 注 的 问 题 就 如 读 者 熟 知 的 Windows 下 的 一 些 调 试 工 具, 如 Visual Studio 自 带 的 设 置 断 点 单 步 跟 踪 等, 都 受 到 了 广 大 用 户 的 赞 赏 那 么, 在 Linux 下 有 什 么 很 好 的 调 试 工 具 呢? gdb 调 试 器 是 一 款 GNU 开 发 组 织 并 发 布 的 UNIX/Linux 下 的 程 序 调 试 工 具 虽 然, 它 没 有 图 形 化 的 友 好 界 面, 但 是 它 强 大 的 功 能 也 足 以 与 微 软 的 Visual Studio 等 工 具 媲 美 下 面 就 请 跟 随 笔 者 一 步 步 学 习 gdb 调 试 器 3.4.1 gdb 使 用 流 程 这 里 给 出 了 一 个 短 小 的 程 序, 由 此 带 领 读 者 熟 悉 gdb 的 使 用 流 程 建 议 读 者 能 够
动 手 实 际 操 作 一 下 首 先, 打 开 Linux 下 的 编 辑 器 vi 或 者 emacs, 编 辑 如 下 代 码 ( 由 于 为 了 更 好 地 熟 悉 gdb 的 操 作, 笔 者 在 此 使 用 vi 编 辑, 希 望 读 者 能 够 参 见 3.3 节 中 对 vi 的 介 绍, 并 熟 练 使 用 vi) /*test.c*/ #include <stdio.h> int sum(int m); int main() { int i, n = 0; sum(50); for(i = 1; i<= 50; i++) { n += i; } printf("the sum of 1-50 is %d \n", n ); } int sum(int m) { int i, n = 0; for (i = 1; i <= m; i++) { n += i; printf("the sum of 1-m is %d\n", n); } } 在 保 存 退 出 后 首 先 使 用 gcc 对 test.c 进 行 编 译, 注 意 一 定 要 加 上 选 项 -g, 这 样 编 译 出 的 可 执 行 代 码 中 才 包 含 调 试 信 息, 否 则 之 后 gdb 无 法 载 入 该 可 执 行 文 件 [root@localhost gdb]# gcc -g test.c -o test 虽 然 这 段 程 序 没 有 错 误, 但 调 试 完 全 正 确 的 程 序 可 以 更 加 了 解 gdb 的 使 用 流 程 接 下 来 就 启 动 gdb 进 行 调 试 注 意,gdb 进 行 调 试 的 是 可 执 行 文 件, 而 不 是 如.c 的 源 代 码, 因 此, 需 要 先 通 过 gcc 编 译 生 成 可 执 行 文 件 才 能 用 gdb 进 行 调 试 [root@localhost gdb]# gdb test GNU gdb Red Hat Linux (6.3.0.0-1.21rh) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain
conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"...using host libthread_db library "/lib/libthread_db.so.1". (gdb) 可 以 看 出, 在 gdb 的 启 动 画 面 中 指 出 了 gdb 的 版 本 号 使 用 的 库 文 件 等 信 息, 接 下 来 就 进 入 了 由 ( gdb) 开 头 的 命 令 行 界 面 了 (1) 查 看 文 件 在 gdb 中 键 入 l ( list) 就 可 以 查 看 所 载 入 的 文 件, 如 下 所 示 注 意 在 gdb 的 命 令 中 都 可 使 用 缩 略 形 式 的 命 令, 如 l 代 表 list, b 代 表 breakpoint, p 代 表 print 等, 读 者 也 可 使 用 help 命 令 查 看 帮 助 信 息 (gdb) l 1 #include <stdio.h> 2 int sum(int m); 3 int main() 4 { 5 int i,n = 0; 6 sum(50); 7 for(i = 1; i <= 50; i++) 8 { 9 n += i; 10 } (gdb) l 11 printf("the sum of 1~50 is %d \n", n ); 12 13 } 14 int sum(int m) 15 { 16 int i, n = 0; 17 for(i = 1; i <= m; i++) 18 { 19 n += i; 20 } 21 printf("the sum of 1~m is = %d\n", n); 20 } 可 以 看 出,gdb 列 出 的 源 代 码 中 明 确 地 给 出 了 对 应 的 行 号, 这 样 就 可 以 大 大 地 方 便 代 码 的 定 位 (2) 设 置 断 点
设 置 断 点 是 调 试 程 序 中 一 个 非 常 重 要 的 手 段, 它 可 以 使 程 序 运 行 到 一 定 位 置 时 暂 停 因 此, 程 序 员 在 该 位 置 处 可 以 方 便 地 查 看 变 量 的 值 堆 栈 情 况 等, 从 而 找 出 代 码 的 症 结 所 在 在 gdb 中 设 置 断 点 非 常 简 单, 只 需 在 b 后 加 入 对 应 的 行 号 即 可 ( 这 是 最 常 用 的 方 式, 另 外 还 有 其 他 方 式 设 置 断 点 ), 如 下 所 示 : (gdb) b 6 Breakpoint 1 at 0x804846d: file test.c, line 6. 要 注 意 的 是, 在 gdb 中 利 用 行 号 设 置 断 点 是 指 代 码 运 行 到 对 应 行 之 前 将 其 停 止, 如 上 例 中, 代 码 运 行 到 第 6 行 之 前 暂 停 ( 并 没 有 运 行 第 6 行 ) (3) 查 看 断 点 情 况 在 设 置 完 断 点 之 后, 用 户 可 以 键 入 info b 来 查 看 设 置 断 点 情 况, 在 gdb 中 可 以 设 置 多 个 断 点 (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0804846d in main at test.c:6 用 户 在 断 点 键 入 backrace ( 只 输 入 bt 即 可 ) 可 以 查 到 调 用 函 数 ( 堆 栈 ) 的 情 况, 这 个 功 能 在 程 序 调 试 之 中 使 用 非 常 广 泛, 经 常 用 于 排 除 错 误 或 者 监 视 调 用 堆 栈 的 情 况 行 */ (gdb) b 19 (gdb) c Breakpoin 2, sum(m=50) at test.c:19 19 printf( The sum of 1-m is %d\n, n); (gdb) bt #0 sum(m=50) at test.c:19 /* 停 在 test.c 的 sum() 函 数, 第 19 #1 0x080483e8 in main() at test.c:6 /* test.c 的 第 6 行 调 用 sum 函 数 */ (4) 运 行 代 码 接 下 来 就 可 运 行 代 码 了,gdb 默 认 从 首 行 开 始 运 行 代 码, 键 入 r ( run) 即 可 ( 若 想 从 程 序 中 指 定 行 开 始 运 行, 可 在 r 后 面 加 上 行 号 ) (gdb) r Starting program: /root/workplace/gdb/test Reading symbols from shared object read from target memory...done. Loaded system supplied DSO at 0x5fb000 Breakpoint 1, main () at test.c:6 6 sum(50); 可 以 看 到, 程 序 运 行 到 断 点 处 就 停 止 了
(5) 查 看 变 量 值 在 程 序 停 止 运 行 之 后, 程 序 员 所 要 做 的 工 作 是 查 看 断 点 处 的 相 关 变 量 值 在 gdb 中 键 入 p + 变 量 值 即 可, 如 下 所 示 : (gdb) p n $1 = 0 (gdb) p i $2 = 134518440 在 此 处, 为 什 么 变 量 i 的 值 为 如 此 奇 怪 的 一 个 数 字 呢? 原 因 就 在 于 程 序 是 在 断 点 设 置 的 对 应 行 之 前 停 止 的, 那 么 在 此 时, 并 没 有 把 i 的 数 值 赋 为 零, 而 只 是 一 个 随 机 的 数 字 但 变 量 n 是 在 第 4 行 赋 值 的, 故 在 此 时 已 经 为 零 小 技 巧 gdb 在 显 示 变 量 值 时 都 会 在 对 应 值 之 前 加 上 $N 标 记, 它 是 当 前 变 量 值 的 引 用 标 记, 所 以 以 后 若 想 再 次 引 用 此 变 量 就 可 以 直 接 写 作 $N, 而 无 需 写 冗 长 的 变 量 名 (6) 单 步 运 行 单 步 运 行 可 以 使 用 命 令 n ( next) 或 s ( step), 它 们 之 间 的 区 别 在 于 : 若 有 函 数 调 用 的 时 候, s 会 进 入 该 函 数 而 n 不 会 进 入 该 函 数 因 此, s 就 类 似 于 Uisual 等 工 具 中 的 step in, n 类 似 与 Uisual 等 工 具 中 的 step over 它 们 的 使 用 如 下 所 示 : (gdb) n The sum of 1-m is 1275 7 for (i = 1; i <= 50; i++) (gdb) s sum (m=50) at test.c:16 16 int i, n = 0; 可 见, 使 用 n 后, 程 序 显 示 函 数 sum() 的 运 行 结 果 并 向 下 执 行, 而 使 用 s 后 则 进 入 sum() 函 数 之 中 单 步 运 行 (7) 恢 复 程 序 运 行 在 查 看 完 所 需 变 量 及 堆 栈 情 况 后, 就 可 以 使 用 命 令 c ( continue) 恢 复 程 序 的 正 常 运 行 了 这 时, 它 会 把 剩 余 还 未 执 行 的 程 序 执 行 完, 并 显 示 剩 余 程 序 中 的 执 行 结 果 以 下 是 之 前 使 用 n 命 令 恢 复 后 的 执 行 结 果 : (gdb) c Continuing. The sum of 1-50 is :1275 Program exited with code 031. 可 以 看 出, 程 序 在 运 行 完 后 退 出, 之 后 程 序 处 于 停 止 状 态
小 知 识 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 在 gdb 中, 程 序 的 运 行 状 态 有 运 行 暂 停 和 停 止 3 种, 其 中 暂 停 状 态 为 程 序 遇 到 了 断 点 或 观 察 点 之 类 的, 程 序 暂 时 停 止 运 行, 而 此 时 函 数 的 地 址 函 数 参 数 函 数 内 的 局 部 变 量 都 会 被 压 入 栈 (Stack) 中 故 在 这 种 状 态 下 可 以 查 看 函 数 的 变 量 值 等 各 种 属 性 但 在 函 数 处 于 停 止 状 态 之 后, 栈 就 会 自 动 撤 消, 它 也 就 无 法 查 看 各 种 信 息 了 3.4.2 gdb 基 本 命 令 gdb 的 命 令 可 以 通 过 查 看 help 进 行 查 找, 由 于 gdb 的 命 令 很 多, 因 此 gdb 的 help 将 其 分 成 了 很 多 种 类 (class), 用 户 可 以 通 过 进 一 步 查 看 相 关 class 找 到 相 应 命 令, 如 下 所 示 : (gdb) help List of classes of commands: aliases -- Aliases of other commands breakpoints -- Making program stop at certain points data -- Examining data files -- Specifying and examining files internals -- Maintenance commands Type "help"followedbyaclassnameforalistofcommandsinthatclass. Type "help" followed by command name for full documentation. Command name abbreviations are allowed if unambiguous. 上 述 列 出 了 gdb 各 个 分 类 的 命 令, 注 意 底 部 的 加 粗 部 分 说 明 其 为 分 类 命 令 接 下 来 可 以 具 体 查 找 各 分 类 的 命 令, 如 下 所 示 : (gdb) help data Examining data. List of commands: call -- Call a function in the program delete display -- Cancel some expressions to be displayed when program stops delete mem -- Delete memory region disabledisplay--disablesomeexpressionstobedisplayedwhenprogram stops Type "help" followed by command name for full documentation. Command name abbreviations are allowed if unambiguous.
若 用 户 想 要 查 找 call 命 令, 就 可 键 入 help call (gdb) help call Call a function in the program. The argument is the function name and arguments, in the notation of the current working language. The result is printed and saved in the value history, if it is not void. 当 然, 若 用 户 已 知 命 令 名, 直 接 键 入 help [command] 也 是 可 以 的 gdb 中 的 命 令 主 要 分 为 以 下 几 类 : 工 作 环 境 相 关 命 令 设 置 断 点 与 恢 复 命 令 源 代 码 查 看 命 令 查 看 运 行 数 据 相 关 命 令 及 修 改 运 行 参 数 命 令 以 下 就 分 别 对 这 几 类 命 令 进 行 讲 解 1. 工 作 环 境 相 关 命 令 gdb 中 不 仅 可 以 调 试 所 运 行 的 程 序, 而 且 还 可 以 对 程 序 相 关 的 工 作 环 境 进 行 相 应 的 设 定, 甚 至 还 可 以 使 用 shell 中 的 命 令 进 行 相 关 的 操 作, 其 功 能 极 其 强 大 gdb 常 见 工 作 环 境 相 关 命 令 如 表 3.11 所 示 表 3.11 gdb 工 作 环 境 相 关 命 令 命 令 格 式 含 义 set args 运 行 时 的 参 数 指 定 运 行 时 参 数, 如 set args 2 show args 查 看 设 置 好 的 运 行 参 数 Path dir 设 定 程 序 的 运 行 路 径 show paths 查 看 程 序 的 运 行 路 径 set environment var [=value] 设 置 环 境 变 量 show environment [var] 查 看 环 境 变 量 cd dir 进 入 dir 目 录, 相 当 于 shell 中 的 cd 命 令 Pwd 显 示 当 前 工 作 目 录 shell command 运 行 shell 的 command 命 令 2. 设 置 断 点 与 恢 复 命 令 gdb 中 设 置 断 点 与 恢 复 的 常 见 命 令 如 表 3.12 所 示 表 3.12 gdb 设 置 断 点 与 恢 复 相 关 命 令 命 令 格 式 含 义 Info b break [ 文 件 名 :] 行 号 或 函 数 名 < 条 件 表 达 式 > tbreak [ 文 件 名 :] 行 号 或 函 数 名 < 条 件 表 达 式 > delete [ 断 点 号 ] 查 看 所 设 断 点 设 置 断 点 设 置 临 时 断 点, 到 达 后 被 自 动 删 除 删 除 指 定 断 点, 其 断 点 号 为 info b 中 的 第 一 栏 若 缺 省 断 点 号 则 删 除 所 有 断 点
disable [ 断 点 号 ] enable [ 断 点 号 ] condition [ 断 点 号 ] < 条 件 表 达 式 > ignore [ 断 点 号 ]<num> Step Next Finish C 停 止 指 定 断 点, 使 用 info b 仍 能 查 看 此 断 点 同 delete 一 样, 若 缺 省 断 点 号 则 停 止 所 有 断 点 激 活 指 定 断 点, 即 激 活 被 disable 停 止 的 断 点 修 改 对 应 断 点 的 条 件 在 程 序 执 行 中, 忽 略 对 应 断 点 num 次 单 步 恢 复 程 序 运 行, 且 进 入 函 数 调 用 单 步 恢 复 程 序 运 行, 但 不 进 入 函 数 调 用 运 行 程 序, 直 到 当 前 函 数 完 成 返 回 继 续 执 行 函 数, 直 到 函 数 结 束 或 遇 到 新 的 断 点 设 置 断 点 在 gdb 的 调 试 中 非 常 重 要, 下 面 着 重 讲 解 gdb 中 设 置 断 点 的 方 法 gdb 中 设 置 断 点 有 多 种 方 式 : 其 一 是 按 行 设 置 断 点 ; 另 外 还 可 以 设 置 函 数 断 点 和 条 件 断 点 下 面 具 体 介 绍 后 两 种 设 置 断 点 的 方 法 1 函 数 断 点 gdb 中 按 函 数 设 置 断 点 只 需 把 函 数 名 列 在 命 令 b 之 后, 如 下 所 示 : (gdb) b test.c:sum ( 可 以 简 化 为 b sum) Breakpoint 1 at 0x80484ba: file test.c, line 16. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x080484ba in sum at test.c:16 要 注 意 的 是, 此 时 的 断 点 实 际 是 在 函 数 的 定 义 处, 也 就 是 在 16 行 处 ( 注 意 第 16 行 还 未 执 行 ) 2 条 件 断 点 gdb 中 设 置 条 件 断 点 的 格 式 为 :b 行 数 或 函 数 名 if 表 达 式 具 体 实 例 如 下 所 示 : (gdb) b 8 if i==10 Breakpoint 1 at 0x804848c: file test.c, line 8. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0804848c in main at test.c:8 stop only if i == 10 (gdb) r Starting program: /home/yul/test The sum of 1-m is 1275 Breakpoint 1, main () at test.c:9 9 n += i; (gdb) p i $1 = 10
可 以 看 到, 该 例 中 在 第 8 行 ( 也 就 是 运 行 完 第 7 行 的 for 循 环 ) 设 置 了 一 个 i==0 的 条 件 断 点, 在 程 序 运 行 之 后 可 以 看 出, 程 序 确 实 在 i 为 10 时 暂 停 运 行 3.gdb 中 源 码 查 看 相 关 命 令 在 gdb 中 可 以 查 看 源 码 以 方 便 其 他 操 作, 它 的 常 见 相 关 命 令 如 表 3.13 所 示 表 3.13 gdb 源 码 查 看 相 关 相 关 命 令 命 令 格 式 含 义 list < 行 号 > < 函 数 名 > file [ 文 件 名 ] forward-search 正 则 表 达 式 reverse-search 正 则 表 达 式 dir DIR show directories info line 查 看 指 定 位 置 代 码 加 载 指 定 文 件 源 代 码 的 前 向 搜 索 源 代 码 的 后 向 搜 索 将 路 径 DIR 添 加 到 源 文 件 搜 索 的 路 径 的 开 头 显 示 源 文 件 的 当 前 搜 索 路 径 显 示 加 载 到 gdb 内 存 中 的 代 码 4.gdb 中 查 看 运 行 数 据 相 关 命 令 gdb 中 查 看 运 行 数 据 是 指 当 程 序 处 于 运 行 或 暂 停 状 态 时, 可 以 查 看 的 变 量 及 表 达 式 的 信 息, 其 常 见 命 令 如 表 3.14 所 示 表 3.14 gdb 查 看 运 行 数 据 相 关 命 令 命 令 格 式 含 义 print 表 达 式 变 量 x <n/f/u> display 表 达 式 backtrace 查 看 程 序 运 行 时 对 应 表 达 式 和 变 量 的 值 查 看 内 存 变 量 内 容 其 中 n 为 整 数 表 示 显 示 内 存 的 长 度,f 表 示 显 示 的 格 式,u 表 示 从 当 前 地 址 往 后 请 求 显 示 的 字 节 数 设 定 在 单 步 运 行 或 其 他 情 况 中, 自 动 显 示 的 对 应 表 达 式 的 内 容 查 看 当 前 栈 的 情 况, 即 可 以 查 到 哪 些 被 调 用 的 函 数 尚 未 返 回 5.gdb 中 修 改 运 行 参 数 相 关 命 令 gdb 还 可 以 修 改 运 行 时 的 参 数, 并 使 该 变 量 按 照 用 户 当 前 输 入 的 值 继 续 运 行 它 的 设 置 方 法 为 : 在 单 步 执 行 的 过 程 中, 键 入 命 令 set 变 量 = 设 定 值 这 样, 在 此 之 后, 程 序 就 会 按 照 该 设 定 的 值 运 行 了 下 面, 笔 者 结 合 上 一 节 的 代 码 将 n 的 初 始 值 设 为 4, 其 代 码 如 下 所 示 : (gdb) b 7 Breakpoint 5 at 0x804847a: file test.c, line 7. (gdb) r Starting program: /home/yul/test
The sum of 1-m is 1275 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 Breakpoint 5, main () at test.c:7 7 for(i=1; i <= 50; i++) (gdb) set n=4 (gdb) c Continuing. The sum of 1-50 is 1279 Program exited with code 031. 可 以 看 到, 最 后 的 运 行 结 果 确 实 比 之 前 的 值 大 了 4 注 意 gdb 使 用 时 的 注 意 点 : 在 gcc 编 译 选 项 中 一 定 要 加 入 -g 只 有 在 代 码 处 于 运 行 或 暂 停 状 态 时 才 能 查 看 变 量 值 设 置 断 点 后 程 序 在 指 定 行 之 前 停 止 3.5 make 工 程 管 理 器 到 此 为 止, 读 者 已 经 了 解 了 如 何 在 Linux 下 使 用 编 辑 器 编 写 代 码, 如 何 使 用 gcc 把 代 码 编 译 成 可 执 行 文 件, 还 学 习 了 如 何 使 用 gdb 来 调 试 程 序, 那 么, 所 有 的 工 作 看 似 已 经 完 成 了, 为 什 么 还 需 要 make 这 个 工 程 管 理 器 呢? 所 谓 工 程 管 理 器, 顾 名 思 义, 是 用 于 管 理 较 多 的 文 件 读 者 可 以 试 想 一 下, 由 成 百 上 千 个 文 件 构 成 的 项 目, 如 果 其 中 只 有 一 个 或 少 数 几 个 文 件 进 行 了 修 改, 按 照 之 前 所 学 的 gcc 编 译 工 具, 就 不 得 不 把 这 所 有 的 文 件 重 新 编 译 一 遍, 因 为 编 译 器 并 不 知 道 哪 些 文 件 是 最 近 更 新 的, 而 只 知 道 需 要 包 含 这 些 文 件 才 能 把 源 代 码 编 译 成 可 执 行 文 件, 于 是, 程 序 员 就 不 得 不 重 新 输 入 数 目 如 此 庞 大 的 文 件 名 以 完 成 最 后 的 编 译 工 作 编 译 过 程 分 为 编 译 汇 编 链 接 阶 段, 其 中 编 译 阶 段 仅 检 查 语 法 错 误 以 及 函 数 与 变 量 是 否 被 正 确 地 声 明 了, 在 链 接 阶 段 则 主 要 完 成 函 数 链 接 和 全 局 变 量 的 链 接 因 此, 那 些 没 有 改 动 的 源 代 码 根 本 不 需 要 重 新 编 译, 而 只 要 把 它 们 重 新 链 接 进 去 就 可 以 了 所 以, 人 们 就 希 望 有 一 个 工 程 管 理 器 能 够 自 动 识 别 更 新 了 的 文 件 代 码, 而 不 需 要 重 复 输 入 冗 长 的 命 令 行, 这 样,make 工 程 管 理 器 就 应 运 而 生 了 实 际 上,make 工 程 管 理 器 也 就 是 个 自 动 编 译 管 理 器, 这 里 的 自 动 是 指 它 能 够 根 据 文 件 时 间 戳 自 动 发 现 更 新 过 的 文 件 而 减 少 编 译 的 工 作 量, 同 时, 它 通 过 读 入 makefile 文 件 的 内 容 来 执 行 大 量 的 编 译 工 作 用 户 只 需 编 写 一 次 简 单 的 编 译 语 句 就 可 以 了 它 大 大 提 高 了 实 际 项 目 的 工 作 效 率, 而 且 几 乎 所 有 Linux 下 的 项 目 编 程 均 会 涉 及 它, 希 望 读 者 能 够 认 真 学 习 本 节 内 容
3.5.1 makefile 基 本 结 构 makefile 是 make 读 入 的 惟 一 配 置 文 件, 因 此 本 节 的 内 容 实 际 就 是 讲 述 makefile 的 编 写 规 则 在 一 个 makefile 中 通 常 包 含 如 下 内 容 : 需 要 由 make 工 具 创 建 的 目 标 体 (target), 通 常 是 目 标 文 件 或 可 执 行 文 件 ; 要 创 建 的 目 标 体 所 依 赖 的 文 件 (dependency_file); 创 建 每 个 目 标 体 时 需 要 运 行 的 命 令 (command), 这 一 行 必 须 以 制 表 符 (tab 键 ) 开 头 它 的 格 式 为 : target: dependency_files command /* 该 行 必 须 以 tab 键 开 头 */ 例 如, 有 两 个 文 件 分 别 为 hello.c 和 hello.h, 创 建 的 目 标 体 为 hello.o, 执 行 的 命 令 为 gcc 编 译 指 令 :gcc c hello.c, 那 么, 对 应 的 makefile 就 可 以 写 为 : #The simplest example hello.o: hello.c hello.h gcc c hello.c o hello.o 接 着 就 可 以 使 用 make 了 使 用 make 的 格 式 为 :make target, 这 样 make 就 会 自 动 读 入 makefile( 也 可 以 是 首 字 母 大 写 的 Makefile) 并 执 行 对 应 target 的 command 语 句, 并 会 找 到 相 应 的 依 赖 文 件 如 下 所 示 : 体 [root@localhost makefile]# make hello.o gcc c hello.c o hello.o [root@localhost makefile]# ls hello.c hello.h hello.o makefile 可 以 看 到,makefile 执 行 了 hello.o 对 应 的 命 令 语 句, 并 生 成 了 hello.o 目 标 注 意 在 makefile 中 的 每 一 个 command 前 必 须 有 Tab 符, 否 则 在 运 行 make 命 令 时 会 出 错 3.5.2 makefile 变 量 上 面 示 例 的 makefile 在 实 际 中 是 几 乎 不 存 在 的, 因 为 它 过 于 简 单, 仅 包 含 两 个 文 件 和 一 个 命 令, 在 这 种 情 况 下 完 全 不 必 要 编 写 makefile 而 只 需 在 shell 中 直 接 输 入 即 可, 在 实 际 中 使 用 的 makefile 往 往 是 包 含 很 多 的 文 件 和 命 令 的, 这 也 是 makefile 产 生 的 原 因 下 面 就 可 给 出 稍 微 复 杂 一 些 的 makefile 进 行 讲 解 david:kang.o yul.o gcc kang.o bar.o -o myprog kang.o : kang.c kang.h head.h gcc Wall O -g c kang.c -o kang.o yul.o : bar.c head.h
gcc - Wall O -g c yul.c -o yul.o 在 这 个 makefile 中 有 3 个 目 标 体 (target), 分 别 为 david kang.o 和 yul.o, 其 中 第 一 个 目 标 体 的 依 赖 文 件 就 是 后 两 个 目 标 体 如 果 用 户 使 用 命 令 make david, 则 make 管 理 器 就 是 找 到 david 目 标 体 开 始 执 行 这 时,make 会 自 动 检 查 相 关 文 件 的 时 间 戳 首 先, 在 检 查 kang.o yul.o 和 david 3 个 文 件 的 时 间 戳 之 前, 它 会 向 下 查 找 那 些 把 kang.o 或 yul.o 作 为 目 标 文 件 的 时 间 戳 比 如, kang.o 的 依 赖 文 件 为 kang.c kang.h head.h 如 果 这 些 文 件 中 任 何 一 个 的 时 间 戳 比 kang.o 新, 则 命 令 gcc Wall O -g c kang.c -o kang.o 将 会 执 行, 从 而 更 新 文 件 kang.o 在 更 新 完 kang.o 或 yul.o 之 后, make 会 检 查 最 初 的 kang.o yul.o 和 david 3 个 文 件, 只 要 文 件 kang.o 或 yul.o 中 的 至 少 有 一 个 文 件 的 时 间 戳 比 david 新, 则 第 二 行 命 令 就 会 被 执 行 这 样,make 就 完 成 了 自 动 检 查 时 间 戳 的 工 作, 开 始 执 行 编 译 工 作 这 也 就 是 make 工 作 的 基 本 流 程 接 下 来, 为 了 进 一 步 简 化 编 辑 和 维 护 makefile,make 允 许 在 makefile 中 创 建 和 使 用 变 量 变 量 是 在 makefile 中 定 义 的 名 字, 用 来 代 替 一 个 文 本 字 符 串, 该 文 本 字 符 串 称 为 该 变 量 的 值 在 具 体 要 求 下, 这 些 值 可 以 代 替 目 标 体 依 赖 文 件 命 令 以 及 makefile 文 件 中 其 他 部 分 在 makefile 中 的 变 量 定 义 有 两 种 方 式 : 一 种 是 递 归 展 开 方 式, 另 一 种 是 简 单 方 式 递 归 展 开 方 式 定 义 的 变 量 是 在 引 用 该 变 量 时 进 行 替 换 的, 即 如 果 该 变 量 包 含 了 对 其 他 变 量 的 引 用, 则 在 引 用 该 变 量 时 一 次 性 将 内 嵌 的 变 量 全 部 展 开, 虽 然 这 种 类 型 的 变 量 能 够 很 好 地 完 成 用 户 的 指 令, 但 是 它 也 有 严 重 的 缺 点, 如 不 能 在 变 量 后 追 加 内 容 ( 因 为 语 句 :CFLAGS = $(CFLAGS) -O 在 变 量 扩 展 过 程 中 可 能 导 致 无 穷 循 环 ) 为 了 避 免 上 述 问 题, 简 单 扩 展 型 变 量 的 值 在 定 义 处 展 开, 并 且 只 展 开 一 次, 因 此 它 不 包 含 任 何 对 其 他 变 量 的 引 用, 从 而 消 除 变 量 的 嵌 套 引 用 递 归 展 开 方 式 的 定 义 格 式 为 :VAR=var 简 单 扩 展 方 式 的 定 义 格 式 为 :VAR:=var make 中 的 变 量 使 用 均 使 用 的 格 式 为 :$(VAR) 注 意 变 量 名 是 不 包 括 : # = 以 及 结 尾 空 格 的 任 何 字 符 串 同 时, 变 量 名 中 包 含 字 母 数 字 以 及 下 划 线 以 外 的 情 况 应 尽 量 避 免, 因 为 它 们 可 能 在 将 来 被 赋 予 特 别 的 含 义 变 量 名 是 大 小 写 敏 感 的, 例 如 变 量 名 foo FOO 和 Foo 代 表 不 同 的 变 量 推 荐 在 makefile 内 部 使 用 小 写 字 母 作 为 变 量 名, 预 留 大 写 字 母 作 为 控 制 隐 含 规 则 参 数 或 用 户 重 载 命 令 选 项 参 数 的 变 量 名 下 面 给 出 了 上 例 中 用 变 量 替 换 修 改 后 的 makefile, 这 里 用 OBJS 代 替 kang.o 和 yul.o, 用 CC 代 替 gcc, 用 CFLAGS 代 替 -Wall -O g 这 样 在 以 后 修 改 时, 就 可 以 只 修 改 变 量 定 义, 而 不 需 要 修 改 下 面 的 定 义 实 体, 从 而 大 大 简 化 了 makefile 维 护 的 工
作 量 经 变 量 替 换 后 的 makefile 如 下 所 示 : OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) $(CC) $(OBJS) -o david kang.o : kang.c kang.h $(CC) $(CFLAGS) -c kang.c -o kang.o yul.o : yul.c yul.h $(CC) $(CFLAGS) -c yul.c -o yul.o 可 以 看 到, 此 处 变 量 是 以 递 归 展 开 方 式 定 义 的 makefile 中 的 变 量 分 为 用 户 自 定 义 变 量 预 定 义 变 量 自 动 变 量 及 环 境 变 量 如 上 例 中 的 OBJS 就 是 用 户 自 定 义 变 量, 自 定 义 变 量 的 值 由 用 户 自 行 设 定, 而 预 定 义 变 量 和 自 动 变 量 为 通 常 在 makefile 都 会 出 现 的 变 量, 它 们 的 一 部 分 有 默 认 值, 也 就 是 常 见 的 设 定 值, 当 然 用 户 可 以 对 其 进 行 修 改 预 定 义 变 量 包 含 了 常 见 编 译 器 汇 编 器 的 名 称 及 其 编 译 选 项 表 3.15 列 出 了 makefile 中 常 见 预 定 义 变 量 及 其 部 分 默 认 值 表 3.15 makefile 中 常 见 的 预 定 义 变 量 预 定 义 变 量 含 义 AR AS CC 库 文 件 维 护 程 序 的 名 称, 默 认 值 为 ar 汇 编 程 序 的 名 称, 默 认 值 为 as C 编 译 器 的 名 称, 默 认 值 为 cc CPP C 预 编 译 器 的 名 称, 默 认 值 为 $(CC) E CXX FC C++ 编 译 器 的 名 称, 默 认 值 为 g++ Fortran 编 译 器 的 名 称, 默 认 值 为 f77 RM 文 件 删 除 程 序 的 名 称, 默 认 值 为 rm f ARFLAGS ASFLAGS CFLAGS CPPFLAGS CXXFLAGS FFLAGS 库 文 件 维 护 程 序 的 选 项, 无 默 认 值 汇 编 程 序 的 选 项, 无 默 认 值 C 编 译 器 的 选 项, 无 默 认 值 C 预 编 译 的 选 项, 无 默 认 值 C++ 编 译 器 的 选 项, 无 默 认 值 Fortran 编 译 器 的 选 项, 无 默 认 值 可 以 看 出, 上 例 中 的 CC 和 CFLAGS 是 预 定 义 变 量, 其 中 由 于 CC 没 有 采 用 默 认 值, 因 此, 需 要 把 CC=gcc 明 确 列 出 来 由 于 常 见 的 gcc 编 译 语 句 中 通 常 包 含 了 目 标 文 件 和 依 赖 文 件, 而 这 些 文 件 在
makefile 文 件 中 目 标 体 所 在 行 已 经 有 所 体 现, 因 此, 为 了 进 一 步 简 化 makefile 的 编 写, 就 引 入 了 自 动 变 量 自 动 变 量 通 常 可 以 代 表 编 译 语 句 中 出 现 目 标 文 件 和 依 赖 文 件 等, 并 且 具 有 本 地 含 义 ( 即 下 一 语 句 中 出 现 的 相 同 变 量 代 表 的 是 下 一 语 句 的 目 标 文 件 和 依 赖 文 件 ) 表 3.16 列 出 了 makefile 中 常 见 的 自 动 变 量 表 3.16 makefile 中 常 见 的 自 动 变 量 自 动 变 量 含 义 $* 不 包 含 扩 展 名 的 目 标 文 件 名 称 $+ 所 有 的 依 赖 文 件, 以 空 格 分 开, 并 以 出 现 的 先 后 为 序, 可 能 包 含 重 复 的 依 赖 文 件 $< 第 一 个 依 赖 文 件 的 名 称 $? 所 有 时 间 戳 比 目 标 文 件 晚 的 依 赖 文 件, 并 以 空 格 分 开 $@ 目 标 文 件 的 完 整 名 称 $^ 所 有 不 重 复 的 依 赖 文 件, 以 空 格 分 开 $% 如 果 目 标 是 归 档 成 员, 则 该 变 量 表 示 目 标 的 归 档 成 员 名 称 自 动 变 量 的 书 写 比 较 难 记, 但 是 在 熟 练 了 之 后 使 用 会 非 常 方 便, 请 读 者 结 合 下 例 中 的 自 动 变 量 改 写 的 makefile 进 行 记 忆 OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) $(CC) $^ -o $@ kang.o : kang.c kang.h $(CC) $(CFLAGS) -c $< -o $@ yul.o : yul.c yul.h $(CC) $(CFLAGS) -c $< -o $@ 另 外, 在 makefile 中 还 可 以 使 用 环 境 变 量 使 用 环 境 变 量 的 方 法 相 对 比 较 简 单, make 在 启 动 时 会 自 动 读 取 系 统 当 前 已 经 定 义 了 的 环 境 变 量, 并 且 会 创 建 与 之 具 有 相 同 名 称 和 数 值 的 变 量 但 是, 如 果 用 户 在 makefile 中 定 义 了 相 同 名 称 的 变 量, 那 么 用 户 自 定 义 变 量 将 会 覆 盖 同 名 的 环 境 变 量 3.5.3 makefile 规 则 makefile 的 规 则 是 make 进 行 处 理 的 依 据, 它 包 括 了 目 标 体 依 赖 文 件 及 其 之 间 的 命 令 语 句 在 上 面 的 例 子 中, 都 显 式 地 指 出 了 makefile 中 的 规 则 关 系, 如 $(CC) $(CFLAGS) -c $< -o $@, 但 为 了 简 化 makefile 的 编 写,make 还 定 义 了 隐 式 规 则 和 模 式 规 则, 下 面 就 分 别 对 其 进 行 讲 解 1. 隐 式 规 则 隐 含 规 则 能 够 告 诉 make 怎 样 使 用 传 统 的 规 则 完 成 任 务, 这 样, 当 用 户 使 用 它 们
时 就 不 必 详 细 指 定 编 译 的 具 体 细 节, 而 只 需 把 目 标 文 件 列 出 即 可 make 会 自 动 搜 索 隐 式 规 则 目 录 来 确 定 如 何 生 成 目 标 文 件 如 上 例 就 可 以 写 成 : OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) $(CC) $^ -o $@ 为 什 么 可 以 省 略 后 两 句 呢? 因 为 make 的 隐 式 规 则 指 出 : 所 有.o 文 件 都 可 自 动 由.c 文 件 使 用 命 令 $(CC) $(CPPFLAGS) $(CFLAGS) -c file.c o file.o 来 生 成 这 样 kang.o 和 yul.o 就 会 分 别 通 过 调 用 $(CC) $(CFLAGS) -c kang.c -o kang.o 和 $(CC) $(CFLAGS) -c yul.c -o yul.o 来 生 成 注 意 在 隐 式 规 则 只 能 查 找 到 相 同 文 件 名 的 不 同 后 缀 名 文 件, 如 kang.o 文 件 必 须 由 kang.c 文 件 生 成 表 3.17 给 出 了 常 见 的 隐 式 规 则 目 录 表 3.17 对 应 语 言 后 缀 名 C 编 译 :.c 变 为.o C++ 编 译 :.cc 或.C 变 为.o Pascal 编 译 :.p 变 为.o Fortran 编 译 :.r 变 为 -o makefile 中 常 见 隐 式 规 则 目 录 隐 式 规 则 $(CC) c $(CPPFLAGS) $(CFLAGS) $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $(PC) -c $(PFLAGS) $(FC) -c $(FFLAGS) 2. 模 式 规 则 模 式 规 则 是 用 来 定 义 相 同 处 理 规 则 的 多 个 文 件 的 它 不 同 于 隐 式 规 则, 隐 式 规 则 仅 仅 能 够 用 make 默 认 的 变 量 来 进 行 操 作, 而 模 式 规 则 还 能 引 入 用 户 自 定 义 变 量, 为 多 个 文 件 建 立 相 同 的 规 则, 从 而 简 化 makefile 的 编 写 模 式 规 则 的 格 式 类 似 于 普 通 规 则, 这 个 规 则 中 的 相 关 文 件 前 必 须 用 % 标 明 使 用 模 式 规 则 修 改 后 的 makefile 的 编 写 如 下 : OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) %.o : %.c $(CC) $^ -o $@ $(CC) $(CFLAGS) -c $< -o $@ 3.5.4 make 管 理 器 的 使 用 使 用 make 管 理 器 非 常 简 单, 只 需 在 make 命 令 的 后 面 键 入 目 标 名 即 可 建 立 指 定 的 目 标, 如 果 直 接 运 行 make, 则 建 立 makefile 中 的 第 一 个 目 标
此 外 make 还 有 丰 富 的 命 令 行 选 项, 可 以 完 成 各 种 不 同 的 功 能 表 3.18 列 出 了 常 用 的 make 命 令 行 选 项 表 3.18 make 的 命 令 行 选 项 命 令 格 式 含 义 -C dir 读 入 指 定 目 录 下 的 makefile -f file 读 入 当 前 目 录 下 的 file 文 件 作 为 makefile -I 忽 略 所 有 的 命 令 执 行 错 误 -I dir 指 定 被 包 含 的 makefile 所 在 目 录 -n 只 打 印 要 执 行 的 命 令, 但 不 执 行 这 些 命 令 -p 显 示 make 变 量 数 据 库 和 隐 含 规 则 -s 在 执 行 命 令 时 不 显 示 命 令 -w 如 果 make 在 执 行 过 程 中 改 变 目 录, 则 打 印 当 前 目 录 名 3.6 使 用 autotools 在 上 一 小 节, 读 者 已 经 了 解 到 了 make 项 目 管 理 器 的 强 大 功 能 的 确,makefile 可 以 帮 助 make 完 成 它 的 使 命, 但 要 承 认 的 是, 编 写 makefile 确 实 不 是 一 件 轻 松 的 事, 尤 其 对 于 一 个 较 大 的 项 目 而 言 更 是 如 此 那 么, 有 没 有 一 种 轻 松 的 手 段 生 成 makefile 而 同 时 又 能 让 用 户 享 受 make 的 优 越 性 呢? 本 节 要 讲 的 autotools 系 列 工 具 正 是 为 此 而 设 的, 它 只 需 用 户 输 入 简 单 的 目 标 文 件 依 赖 文 件 文 件 目 录 等 就 可 以 轻 松 地 生 成 makefile 了, 这 无 疑 是 广 大 用 户 所 希 望 的 另 外, 这 些 工 具 还 可 以 完 成 系 统 配 置 信 息 的 收 集, 从 而 可 以 方 便 地 处 理 各 种 移 植 性 的 问 题 也 正 是 基 于 此, 现 在 Linux 上 的 软 件 开 发 一 般 都 用 autotools 来 制 作 makefile, 读 者 在 后 面 的 讲 述 中 就 会 了 解 到 3.6.1 autotools 使 用 流 程 正 如 前 面 所 言,autotools 是 系 列 工 具, 读 者 首 先 要 确 认 系 统 是 否 装 了 以 下 工 具 ( 可 以 用 which 命 令 进 行 查 看 ) 这 样 的 aclocal autoscan autoconf autoheader automake 使 用 autotools 主 要 就 是 利 用 各 个 工 具 的 脚 本 文 件 以 生 成 最 后 的 makefile 其 总 体 流 程 是 使 用 aclocal 生 成 一 个 aclocal.m4 文 件, 该 文 件 主 要 处 理 本 地 的 宏 定 义 ; 改 写 configure.scan 文 件, 并 将 其 重 命 名 为 configure.in, 并 使 用 autoconf 文 件 生 成 configure 文 件
接 下 来, 笔 者 将 通 过 一 个 简 单 的 hello.c 例 子 带 领 读 者 熟 悉 autotools 生 成 makefile 的 过 程, 由 于 在 这 过 程 中 会 涉 及 较 多 的 脚 本 文 件, 为 了 更 清 楚 地 了 解 相 互 之 间 的 关 系, 强 烈 建 议 读 者 实 际 动 手 操 作 以 体 会 其 整 个 过 程 1.autoscan 它 会 在 给 定 目 录 及 其 子 目 录 树 中 检 查 源 文 件, 若 没 有 给 出 目 录, 就 在 当 前 目 录 及 其 子 目 录 树 中 进 行 检 查 它 会 搜 索 源 文 件 以 寻 找 一 般 的 移 植 性 问 题 并 创 建 一 个 文 件 configure.scan, 该 文 件 就 是 接 下 来 autoconf 要 用 到 的 configure.in 原 型 如 下 所 示 : [root@localhost automake]# autoscan autom4te: configure.ac: no such file or directory autoscan: /usr/bin/autom4te failed with exit status: 1 [root@localhost automake]# ls autoscan.log configure.scan hello.c 由 上 述 代 码 可 知 autoscan 首 先 会 尝 试 去 读 入 configure.ac ( 同 configure.in 的 配 置 文 件 ) 文 件, 此 时 还 没 有 创 建 该 配 置 文 件, 于 是 它 会 自 动 生 成 一 个 configure.in 的 原 型 文 件 configure.scan 2.autoconf configure.in 是 autoconf 的 脚 本 配 置 文 件, 它 的 原 型 文 件 configure.scan 如 下 所 示 : # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) #The next one is modified by david #AC_INIT(FULL-PACKAGE-NAME,VERSION,BUG-REPORT-ADDRESS) AC_INIT(hello,1.0) # The next one is added by david AM_INIT_AUTOMAKE(hello,1.0) AC_CONFIG_SRCDIR([hello.c]) AC_CONFIG_HEADER([config.h]) # Checks for programs. AC_PROG_CC # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions. AC_CONFIG_FILES([makefile])
AC_OUTPUT 嵌 入 式 Linux 应 用 程 序 开 发 标 准 教 程 第 3 章 Linux 下 C 编 程 基 础 下 面 对 这 个 脚 本 文 件 进 行 解 释 以 # 号 开 始 的 行 是 注 释 AC_PREREQ 宏 声 明 本 文 件 要 求 的 autoconf 版 本, 如 本 例 使 用 的 版 本 2.59 AC_INIT 宏 用 来 定 义 软 件 的 名 称 和 版 本 等 信 息, 在 本 例 中 省 略 了 BUG-REPORT-ADDRESS, 一 般 为 作 者 的 E-mail AM_INIT_AUTOMAKE 是 笔 者 另 加 的, 它 是 automake 所 必 备 的 宏, 使 automake 自 动 生 成 makefile.in, 也 同 前 面 一 样,PACKAGE 是 所 要 产 生 软 件 套 件 的 名 称,VERSION 是 版 本 编 号 AC_CONFIG_SRCDIR 宏 用 来 检 查 所 指 定 的 源 码 文 件 是 否 存 在, 以 及 确 定 源 码 目 录 的 有 效 性 在 此 处 源 码 文 件 为 当 前 目 录 下 的 hello.c AC_CONFIG_HEADER 宏 用 于 生 成 config.h 文 件, 以 便 autoheader 使 用 AC_CONFIG_FILES 宏 用 于 生 成 相 应 的 makefile 文 件 中 间 的 注 释 之 间 可 以 分 别 添 加 用 户 测 试 程 序 测 试 函 数 库 测 试 头 文 件 等 宏 定 义 接 下 来 首 先 运 行 aclocal, 生 成 一 个 aclocal.m4 文 件, 该 文 件 主 要 处 理 本 地 的 宏 定 义 如 下 所 示 : [root@localhost automake]# aclocal 再 接 着 运 行 autoconf, 生 成 configure 可 执 行 文 件 如 下 所 示 : [root@localhost automake]# autoconf [root@localhost automake]# ls aclocal.m4 autom4te.cache autoscan.log configure configure.in hello.c 3.autoheader 接 着 使 用 autoheader 命 令, 它 负 责 生 成 config.h.in 文 件 该 工 具 通 常 会 从 acconfig.h 文 件 中 复 制 用 户 附 加 的 符 号 定 义, 因 为 这 里 没 有 附 加 符 号 定 义, 所 以 不 需 要 创 建 acconfig.h 文 件 如 下 所 示 : [root@localhost automake]# autoheader 4.automake 这 一 步 是 创 建 makefile 很 重 要 的 一 步,automake 要 用 的 脚 本 配 置 文 件 是 makefile.am, 用 户 需 要 自 己 创 建 相 应 的 文 件 之 后,automake 工 具 转 换 成 makefile.in 在 该 例 中, 笔 者 创 建 的 文 件 为 makefile.am, 如 下 所 示 : AUTOMAKE_OPTIONS=foreign bin_programs= hello
hello_sources= hello.c 下 面 对 该 脚 本 文 件 的 对 应 项 进 行 解 释 其 中 的 AUTOMAKE_OPTIONS 为 设 置 automake 的 选 项 GNU 对 自 己 发 布 的 软 件 有 严 格 的 规 范, 比 如 必 须 附 带 许 可 证 声 明 文 件 COPYING 等, 否 则 automake 执 行 时 会 报 错 automake 提 供 了 3 种 软 件 等 级 :foreign gnu 和 gnits, 让 用 户 选 择 采 用, 默 认 等 级 为 gnu 在 本 示 例 中 采 用 foreign 等 级, 它 只 检 测 必 须 的 文 件 bin_programs 定 义 要 产 生 的 执 行 文 件 名 如 果 要 产 生 多 个 执 行 文 件, 每 个 文 件 名 用 空 格 隔 开 hello_sources 定 义 hello 这 个 执 行 程 序 所 需 要 的 原 始 文 件 如 果 hello 这 个 程 序 是 由 多 个 原 始 文 件 所 产 生 的, 则 必 须 把 它 所 用 到 的 所 有 原 始 文 件 都 列 出 来, 并 用 空 格 隔 开 例 如 : 若 目 标 体 hello 需 要 hello.c david.c hello.h 三 个 依 赖 文 件, 则 定 义 hello_sources=hello.c david.c hello.h 要 注 意 的 是, 如 果 要 定 义 多 个 执 行 文 件, 则 对 每 个 执 行 程 序 都 要 定 义 相 应 的 file_sources 接 下 来 可 以 使 用 automake 命 令 来 生 成 configure.in 文 件, 在 这 里 使 用 选 项 -a ( 或 者 adding-missing ) 可 以 让 automake 自 动 添 加 一 些 必 需 的 脚 本 文 件 如 下 所 示 : [root@localhost automake]# automake a( 或 者 automake --add-missing) configure.in: installing './install-sh' configure.in: installing './missing' makefile.am: installing 'depcomp' [root@localhost automake]# ls aclocal.m4 autoscan.log configure.in hello.c makefile.am missing autom4te.cache configure depcomp install-sh makefile.in config.h.in 可 以 看 到, 在 automake 之 后 就 可 以 生 成 configure.in 文 件 5. 运 行 configure 在 这 一 步 中, 通 过 运 行 自 动 配 置 设 置 文 件 configure, 把 makefile.in 变 成 了 最 终 的 makefile 如 下 所 示 : [root@localhost automake]#./configure checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for gawk... gawk checking whether make sets $(MAKE)... yes checking for gcc... gcc checking for C compiler default output file name... a.out
checking whether the C compiler works... yes checking whether we are cross compiling... no checking for suffix of executables... checking for suffix of object files... o checking whether we are using the GNU C compiler... yes checking whether gcc accepts -g... yes checking for gcc option to accept ANSI C... none needed checking for style of include used by make... GNU checking dependency style of gcc... gcc3 configure: creating./config.status config.status: creating makefile config.status: executing depfiles commands 可 以 看 到, 在 运 行 configure 时 收 集 了 系 统 的 信 息, 用 户 可 以 在 configure 命 令 中 对 其 进 行 方 便 的 配 置 在./configure 的 自 定 义 参 数 有 两 种, 一 种 是 开 关 式 (--enable-xxx 或 --disable-xxx), 另 一 种 是 开 放 式, 即 后 面 要 填 入 一 串 字 符 (--with-xxx=yyyy) 参 数 读 者 可 以 自 行 尝 试 其 使 用 方 法 另 外, 读 者 可 以 查 看 同 一 目 录 下 的 config.log 文 件, 以 方 便 调 试 之 用 到 此 为 止,makefile 就 可 以 自 动 生 成 了 回 忆 整 个 步 骤, 用 户 不 再 需 要 定 制 不 同 的 规 则, 而 只 需 要 输 入 简 单 的 文 件 及 目 录 名 即 可, 这 样 就 大 大 方 便 了 用 户 的 使 用 autotools 生 成 makefile 的 流 程 如 图 3.9 所 示 autoscan aclocal configure.scan autoheader aclocal.m4 configure.in config.h.in makefile.am automake configure makefile.in./configure makefile 图 3.9 autotools 生 成 makefile 的 流 程 图 3.6.2 使 用 autotools 所 生 成 的 makefile autotools 生 成 的 makefile 除 具 有 普 通 的 编 译 功 能 外, 还 具 有 以 下 主 要 功 能 ( 感 兴 趣 的 读 者 可 以 查 看 这 个 简 单 的 hello.c 程 序 的 makefile)
1.make 键 入 make 默 认 执 行 make all 命 令, 即 目 标 体 为 all, 其 执 行 情 况 如 下 所 示 : [root@localhost automake]# make if gcc -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.tpo" -c -o hello.o hello.c; \ then mv -f ".deps/hello.tpo" ".deps/hello.po"; else rm -f ".deps/hello.tpo"; exit 1; fi gcc -g -O2 -o hello hello.o 此 时 在 本 目 录 下 就 生 成 了 可 执 行 文 件 hello, 运 行./hello 能 出 现 正 常 结 果, 如 下 所 示 : [root@localhost automake]#./hello Hello!Autoconf! 2.make install 此 时, 会 把 该 程 序 安 装 到 系 统 目 录 中 去, 如 下 所 示 : [root@localhost automake]# make install if gcc -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.tpo" -c -o hello.o hello.c; \ then mv -f ".deps/hello.tpo" ".deps/hello.po"; else rm -f ".deps/hello.tpo"; exit 1; fi gcc -g -O2 -o hello hello.o make[1]: Entering directory '/root/workplace/automake' test -z "/usr/local/bin" mkdir -p -- "/usr/local/bin" /usr/bin/install -c 'hello' '/usr/local/bin/hello' make[1]: Nothing to be done for 'install-data-am'. make[1]: Leaving directory '/root/workplace/automake' 此 时, 若 直 接 运 行 hello, 也 能 出 现 正 确 结 果, 如 下 所 示 : [root@localhost automake]# hello Hello!Autoconf! 3.make clean 此 时,make 会 清 除 之 前 所 编 译 的 可 执 行 文 件 及 目 标 文 件 (object file, *.o), 如 下