第 五 部 分 高 级 主 题
第 11 章 调 试 器
完 成 设 计 和 编 码 后 , 即 开 始 调 试 程 序 , 这 是 软 件 开 发 的 第 三 步 。 一 个 3000 行 的程 序 , 其 编 译 可 达 到 没 有 一 个 警 告 , 然 而 在 运 行 时 却 可 能 崩 溃 , 更 糟 糕 的 是 , 运行 只 是 偶 尔 崩 溃 。 当 程 序 不 能 顺 利 运 行 , 而 又 不 知 道 问 题 的 症 结 所 在 时 , 就 该 使用 调 试 器 来 监 视 此 程 序 的 运 行 了 。
Visual C++ 调 试 器 是 整 个 产 品 中 最 具 特 色 的 一 部 分 , 省 时 省 力 , 简 单 易 用 , 它 可以 帮 助 找 到 在 Windows 软 件 开 发 过 程 中 可 能 遇 到 的 几 乎 一 切 故 障 。 但 调 试 程 序就 像 科 学 一 样 , 它 需 要 技 术 , 需 要 有 清 醒 的 头 脑 和 良 好 的 洞 察 力 , 调 试 器 就 像 显微 镜 一 样 , 因 为 它 能 扩 大 你 的 视 野 , 但 你 必 须 知 道 所 查 找 的 症 结 。
动 态 链 接 库 ,包 括 Active X 控 件 ,对 Visual C++ 调 试 器 来 说 ,并 不 是 特 殊 的 情 况 。调 试 器 轻 松 跨 越 两 个 项 目 之 间 的 边 界 时 , 即 意 味 着 可 以 开 始 调 试 项 目 中 的 程 序 , 然 后 , 当 此 程 序 调 用 动 态 链 接 库 中 的 外 部 函 数 时 , 继 续 进 行 调 试 , 即 使 该 库 和 它的 源 文 件 存 在 于 另 一 项 目 或 子 项 目 中 , 反 之 亦 然 。 在 动 态 链 接 库 的 项 目 中 , 可 以启 动 调 试 会 话 。 在 这 种 情 况 下 , 调 试 器 自 动 运 行 所 调 用 的 应 用 程 序 , 并 且 , 当 执行 流 执 行 了 某 一 库 函 数 时 , 就 会 将 控 制 权 返 回 给 用 户 。
调 试 器 能 够 处 理 多 线 程 和 ActiveX 应 用 程 序 ,并 且 有 能 力 在 调 试 程 序 的 计 算 机 上运 行 。 在 以 后 的 章 节 中 , 我 们 将 会 看 到 这 些 特 殊 的 情 况 。 首 先 , 让 我 们 先 熟 悉 一下 调 试 器 。
调 试 与 发 行
Visual C++ 中 的 程 序 能 产 生 两 种 类 型 的 执 行 代 码 , 称 为 调 试 与 发 行 版 本 , 或 称 之为 “ 目 标 ” 版 本 。 调 试 版 本 是 软 件 开 发 和 检 测 修 改 程 序 的 部 分 , 发 行 版 本 是 最 终的 结 果 , 将 发 行 给 客 户 。 调 试 版 本 较 之 发 行 版 本 要 大 , 运 行 起 来 要 慢 一 些 。 编 译器 在 目 标 文 件 中 填 满 了 符 号 信 息 , 这 些 符 号 信 息 记 录 了 编 译 器 知 道 的 函 数 名 、程序 中 的 变 量 名 和 标 识 的 内 存 地 址 。通 过 读 取 源 文 件 和 包 含 在 执 行 文 件 中 的 符 号 信息 ,调 试 器 能 将 源 代 码 中 的 每 条 流 线 同 相 应 的 可 执 行 映 像 中 的 二 进 制 指 令 联 系 起来 。 调 试 器 运 行 可 执 行 文 件 , 但 必 须 使 用 源 代 码 来 显 示 程 序 的 进 程 。
发 行 版 本 含 有 的 仅 仅 是 编 译 器 优 化 的 可 执 行 指 令 , 没 有 符 号 信 息 。 如 果 你 需 要 , 可 以 在 调 试 器 中 执 行 一 个 发 行 版 本 。 调 试 器 将 会 提 醒 你 文 件 没 有 符 号 数 据 。 同时 ,可 以 执 行 程 序 中 的 调 试 版 本 而 无 须 调 试 器 。这 具 有 实 际 效 果 ,因 为 Visual C++ 以 即 时 ( just-in-tim e) 调 试 而 闻 名 , 在 以 后 的 章 节 中 将 证 明 这 一 点 。 当 运 行 没 有调 试 器 的 程 序 的 调 试 版 本 时 ,W indows 加 载 程 序 将 会 忽 略 文 件 中 的 一 些 外 加 的 符号 信 息 , 从 而 允 许 程 序 正 常 运 行 。 如 果 程 序 产 生 了 错 误 , 而 且 系 统 的 异 常 处 理 导致 控 制 权 返 回 Visual C++, 则 继 续 执 行 调 试 器 。 当 程 序 停 止 时 , 调 试 器 显 示 产 生
错 误 的 指 令 , 并 且 显 示 程 序 停 止 当 前 的 数 据 值 。对 查 找 在 程 序 检 测 期 间 跟 踪 错 误来 说 , 以 及 寻 找 那 些 看 上 去 似 乎 是 随 机 的 , 但 却 是 程 序 运 行 中 的 祸 根 的 错 误 来说 , 这 种 高 级 特 征 尤 其 有 用 。
顺 便 说 一 下 , 如 果 想 保 留 对 你 来 说 比 较 重 要 的 内 容 , 处 理 源 代 码 时 , 就 应 该 处 理程 序 中 的 调 试 版 本 。含 有 符 号 信 息 的 程 序 文 件 要 比 工 程 逆 转 容 易 得 多 ,原 因 是 该文 件 含 有 程 序 中 的 所 有 变 量 名 和 函 数 名 。 它 取 代 了 匿 名 的 反 汇 编 语 句 , 例 如 :
004017ae push ebp 004017af mov ebp,esp
调 试 器 包 括 从 符 号 数 据 中 读 取 的 函 数 名 :
Myclass :: InitiInstance :
004017ae push ebp 004017af mov ebp,esp
虽 然 源 代 码 现 在 没 有 什 么 用 处 了 , 但 是 , 现 在 任 何 人 都 可 以 识 别 名 为 InitInstance
的 函 数 序 码 。
调 试 器 的 使 用
当 你 调 试 一 个 有 小 毛 病 的 程 序 时 , 这 种 程 序 有 时 也 称 之 为 被 调 试 程 序
( debuggee ), 首 先 调 试 器 开 始 运 行 , 接 着 开 始 执 行 你 想 要 调 试 的 程 序 , 当 所 运行 的 程 序 到 达 所 选 定 的 指 令 或 者 改 变 某 一 特 定 的 变 量 时 ,调 试 器 允 许 你 重 新 获 得控 制 权 。 当 此 程 序 被 挂 起 时 , 这 将 会 给 你 一 个 检 查 当 前 数 据 值 的 机 会 , 也 能 确 保控 制 流 沿 着 一 条 所 期 望 的 路 径 进 行 处 理 。
调 试 器 能 够 提 供 大 量 的 信 息 , 这 使 之 看 上 去 更 为 复 杂 。 如 果 你 是 编 程 新 手 , 不 要被 调 试 器 所 吓 倒 , 很 快 , 你 就 会 了 解 每 个 程 序 员 所 学 得 的 知 识 。 一 个 典 型 的 调 试操 作 由 以 下 几 步 组 成 : 首 先 , 确 定 出 现 问 题 的 那 一 段 程 序 , 然 后 给 这 段 程 序 的 第一 个 指 令 加 上 标 记 , 启 动 调 试 器 , 运 行 程 序 , 直 到 控 制 到 达 你 事 先 在 带 有 疑 问 的程 序 段 所 设 置 的 标 记 。 当 调 试 器 停 止 程 序 的 执 行 时 , 可 以 通 过 单 步 操 作 指 令 , 以核 实 每 步 的 影 响 效 果 。
那 么 , 调 试 器 怎 样 知 道 什 么 时 候 中 断 程 序 呢 ? 实 际 上 , 并 不 是 调 试 器 中 断 程 序 , 而 是 程 序 自 己 自 行 中 断 , 当 程 序 运 行 到 你 先 前 设 置 的 标 志 时 便 进 行 中 断 , 这 种 标记 为 断 点 。
断 点
在 W indows 中 , 调 试 器 与 所 运 行 的 程 序 之 间 的 关 系 是 独 一 无 二 的 。 不 可 能 再 找到 两 个 程 序 在 运 行 时 联 系 得 如 此 紧 密 。调 试 器 和 程 序 不 能 够 同 时 运 行 在 一 种 其 他常 规 应 用 程 序 能 同 时 运 行 的 多 任 务 环 境 下 。 当 程 序 运 行 时 , 调 试 器 停 止 工 作 , 当执 行 程 序 触 发 断 点 时 , 它 会 重 新 获 得 控 制 权 。
调 试 器 允 许 你 设 置 两 种 不 同 类 型 的 断 点 ,一 种 断 点 是 基 于 代 码 所 在 的 位 置 , 另 一种 是 基 于 程 序 数 据 。位 置 断 点 是 一 种 与 源 代 码 相 联 系 的 特 殊 指 令 的 标 志 , 类 似 于文 本 编 辑 器 中 的 书 签 。在 所 有 想 彻 底 弄 清 楚 错 误 怎 样 产 生 的 含 有 可 疑 代 码 的 程 序段 中 , 你 设 置 一 个 位 置 断 点 。 当 执 行 程 序 试 图 运 行 所 标 记 的 指 令 时 , 它 就 会 停 止或 者 “ 中 断 ” ( 在 接 下 来 的 章 节 中 我 们 将 会 弄 清 楚 这 是 怎 样 进 行 的 )。
数 据 断 点 取 决 于 数 据 , 而 不 是 代 码 。当 你 怀 疑 某 一 变 量 在 程 序 中 的 某 一 地 方 被 错误 地 改 变 但 又 不 知 道 在 什 么 位 置 时 , 就 可 以 使 用 数 据 断 点 。数 据 断 点 可 以 告 诉 调试 器 , 当 变 量 改 变 或 变 为 某 一 不 确 定 的 值 时 , 就 中 断 执 行 。 例 如 , 当 指 针 重 新 赋值 或 变 量 x 的 值 超 过 了 500 时 。
当 位 置 或 数 据 断 点 被 触 发 时 ,控 制 权 返 回 到 调 试 器 。调 试 器 更 改 显 示 目 前 变 量 值的 窗 口 , 以 及 发 生 中 断 的 源 代 码 所 在 的 程 序 段 。 现 在 , 可 以 单 步 执 行 代 码 , 每 次一 条 指 令 , 以 便 弄 清 楚 变 量 是 怎 样 变 化 的 , 以 及 程 序 的 执 行 状 况 。
断 点 返 回 控 制 权 给 调 试 器 的 方 式
为 了 阐 述 断 点 是 怎 样 工 作 的 ,本 节 讲 述 调 试 器 设 置 断 点 来 中 断 程 序 和 断 点 触 发 时重 新 获 得 控 制 权 的 步 骤 。 讨 论 以 Intel 和 兼 容 处 理 器 为 主 , 但 在 其 他 处 理 器 中 的过 程 与 这 里 所 概 述 的 过 程 类 似 。在 接 下 来 的 讨 论 中 ,“ 程 序 ”仅 指 你 的 应 用 程 序 , 不 会 是 调 试 器 。 这 可 以 避 免 使 用 “ 被 调 试 的 程 序 ” 这 样 绕 口 的 短 语 。 并 且 , “ Debuggee ( 被 调 试 的 程 序 )” 听 起 来 太 像 debugger( 调 试 器 )。
当 设 置 一 个 位 置 断 点 , 或 者 单 步 操 作 从 一 个 C/C++ 指 令 到 另 一 个 指 令 时 , 在 执 行程 序 代 码 块 中 断 的 地 方 , 调 试 器 会 覆 写 一 个 简 单 的 字 节 。 它 存 储 字 节 的 原 始 值 , 然 后 在 它 所 在 的 位 置 写 上 0xCC 。 处 理 器 将 0xCC 解 释 成 INT 3 指 令 , 它 将 会 提醒 处 理 器 去 执 行 相 应 的 中 断 3 系 统 处 理 程 序 , 它 与 Intel 调 用 的 中 断 3 这 个 断 点不 会 重 合 。
写 完 INT 3 指 令 后 , 调 试 器 通 过 调 用 WaitForDebugEvent API 函 数 进 入 睡 眠 状态 , 系 统 将 控 制 权 返 回 给 该 程 序 , 直 到 它 运 行 到 INT 3 指 令 后 才 会 恢 复 正 常 。 在执 行 这 个 指 令 时 , 处 理 器 将 CS 和 EIP 寄 存 器 的 当 前 值 写 到 堆 栈 上 , 并 调 用 系 统的 中 断 3 处 理 程 序 , 这 就 是 将 控 制 权 返 回 给 调 试 器 的 过 程 。 内 核 从WaitForDebugEvent 返 回 ,唤 醒 调 试 器 ,然 后 更 改 它 的 窗 口 ,并 等 待 你 的 指 令( 参见 后 面 的 CS 和 EIP 寄 存 器 说 明 )。
此 时 , 程 序 将 冻 结 , 由 于 它 无 法 接 收 到 CPU 时 间 , 当 你 与 调 试 器 交 互 时 , 程 序
不 能 继 续 执 行 通 过 断 点 。 一 般 来 说 , 在 程 序 中 , 调 试 器 直 接 控 制 程 序 中 的 某 一 线程 , 以 便 别 的 线 程 继 续 接 收 CPU 时 间 , 在 以 后 的 章 节 中 , 将 会 说 明 怎 样 将 线 程挂 起 , 而 不 是 进 行 调 试 。
CS 和 EIP 寄 存 器 说 明
处 理 器 含 有 少 量 的 芯 片 存 储 区 ,即 寄 存 器 , 它 可 以 暂 时 将 所 需 要 处 理 的 指 令 寄 存起 来 。 Intel 处 理 器 含 有 CS 和 EIP 两 个 寄 存 器 , 在 处 理 过 程 中 , 它 们 不 断 指 向 下一 个 指 令 。这 里 简 单 描 述 了 寄 存 器 中 的 指 针 当 产 生 中 断 后 怎 样 引 导 处 理 器 返 回 程序 。
CS 代 表 代 码 段 ,在 32 位 编 程 中 也 可 以 说 是 代 码 选 择 器 , 因 为 寄 存 器 容 纳 指 向 程序 码 基 址 的 值 , 这 是 操 作 系 统 赋 予 的 , EIP ( 扩 展 命 令 指 针 ) 寄 存 器 容 纳 一 个 32 位 偏 移 量 , 它 指 向 处 理 器 将 要 执 行 的 另 一 个 指 令 的 代 码 区 域 。当 它 响 应 某 一 中 断
( 诸 如 INT 3 指令)时 , Intel 处 理 器 在 跳 到 为 中 断 服 务 的 处 理 程 序 函 数 之 前 , 首先 将 CS 和 EIP 寄 存 器 的 内 容 写 到 堆 栈 上 。当 处 理 程 序 完 成 时 ,原 始 的 CS 和 EIP 值 从 堆 栈 中 弹 出 , 告 诉 处 理 器 下 一 步 该 干 什 么 。 也 就 是 说 , 在 离 开 中 断 服 务 时 , 返 回 将 要 执 行 的 程 序 指 令 。 同 CS 和 EIP 一 样 , 含 有 当 前 处 理 器 标 记 的 另 一 种 寄存 器 也 从 堆 栈 中 恢 复 , 以 便 原 始 程 序 恢 复 执 行 它 所 不 知 道 的 ( 而 且 没 有 被 影 响的 ) 中 断 。
发 生 中 断 的 情 况 很 多 , 这 主 要 源 于 执 行 程 序 中 的 INT 指 令 和 硬 件 故 障 。 例 如 , 按 下 键 盘 上 的 一 个 键 , 即 触 发 中 断 。 当 松 开 此 键 时 , 又 会 产 生 另 一 种 中 断 。 系 统
当 恢 复 运 行 程 序 时 , 调 试 器 将 被 0xCC 值 覆 写 的 字 节 重 置 , 并 恢 复 设 置 断 点 处 的原 始 指 令 。 因 为 堆 栈 中 的 EIP 值 现 在 指 向 临 时 的 INT 3 指 令 之 后 的 字 节 , 调 试 器减 少 此 值 , 以 便 能 够 重 新 指 向 原 始 指 令 。 接 着 调 试 器 通 过 调 用ContinueDebugEvent API 函 数 回 到 睡 眠 状 态 。 操 作 系 统 将 寄 存 器 恢 复 到 原 始 数据 , 并 执 行 中 断 3 处 理 程 序 中 的 IRET 指 令 ( 中 断 返 回 )。 处 理 器 从 堆 栈 中 弹 出CS , 并 改 变 EIP 值 , 继 续 恢 复 执 行 被 中 断 的 程 序 , 就 像 什 么 也 没 有 发 生 一 样 。
在 所 有 运 行 的 Windows 程 序 实 例 均 共 享 含 有 程 序 码 的 同 一 内 存 段 时 , 情 况 更 为复 杂 。 由 于 调 试 器 将 INT 3 指 令 写 成 程 序 代 码 , 你 可 能 希 望 程 序 实 例 在 调 试 器 之外 运 行 , 并 且 也 能 触 发 断 点 , 并 进 入 调 试 器 , 但 这 是 不 可 能 发 生 的 。 W indows 提 供 一 种 著 名 的 复 制 写 入 机 制 ,能 将 这 种 情 形 处 理 得 相 当 好 。当 调 试 器 调 用 系 统的 WriteProcessMemory 函 数 , 并 将 INT 3 指 令 写 入 一 页 程 序 码 中 时 , W indows 分 配 一 块 可 写 的 内 存 , 将 代 码 页 复 制 到 新 块 中 , 重 新 映 射 该 页 的 虚 拟 内 存 地 址 , 利 用 复 制 的 页 面 来 完 成 调 试 器 的 写 操 作 。另 外 一 些 在 调 试 器 外 部 运 行 的 程 序 实 例可 从 原 始 码 中 继 续 运 行 , 并 且 不 会 遇 到 INT 3 指 令 。
调 试 器 采 用 不 同 的 方 法 来 捕 获 与 程 序 数 据 中 的 变 量 相 关 联 的 数 据 断 点 ,对 用 来 改
变 变 量 的 每 个 指 令 中 设 置 INT 代 码 是 不 切 实 际 的 , 所 以 , 调 试 器 必 须 单 步 通 过程 序 中 的 每 条 指 令 , 每 次 都 检 查 这 些 变 量 。 如 果 变 量 没 有 变 化 , 调 试 器 就 会 执 行下 一 条 指 令 。可 以 想 像 , 不 间 断 的 中 断 与 检 查 可 能 会 很 大 程 度 地 降 低 被 调 试 程 序的 执 行 速 度 , 由 于 上 千 种 中 断 均 能 够 发 生 , 所 以 , 在 利 用 数 据 断 点 来 调 试 时 , 可能 会 程 序 挂 起 。
- 些 处 理 器 所 提 供 的 特 殊 调 试 寄 存 器 就 在 芯 片 上 ,可 以
使 调 试 器 脱 离 开 这 些 冗 长繁 杂 的 任 务 , 一 个 Intel 处 理 器 拥 有 8 个 调 试 寄 存 器 , 虽 然 它 可 以 监 视 4 种 数 据断 点 的 最 大 值 , 因 为 仅 仅 是 4 个 调 试 寄 存 器 ( DR0 到 DR3 ) 中 的 第 一 个 , 并 容纳 内 存 地 址 。当 你 为 变 量 设 置 数 据 断 点 时 , 调 试 器 将 变 量 的 地 址 写 入 到 处 理 器 的调 试 寄 存 器 中 。接着使用 1 位 标 记 来 编 写 调 试 控 制 寄 存 器 ,以 指 示 处 理 器 去 监 视内 存 写 指 令 。 当 处 理 器 运 行 程 序 时 , 它 会 不 断 地 检 查 每 条 指 令 , 以 弄 清 楚 是 否 它已 写 到 调 试 寄 存 器 所 指 向 的 内 存 , 如 果 是 这 样 , 处 理 器 会 产 生 中 断 1 , 称 为 跟 踪中 断 或 调 试 中 断 , 通 过 中 断 处 理 程 序 将 控 制 权 返 回 给 操 作 系 统 。 当 系 统 从WaitForDebugEvent 返 回 时 , 调 试 器 从 DR6 状 态 寄 存 器 读 取 信 息 , 以 确 定 四 个 断点 中 究 竟 是 哪 个 触 发 了 断 点 。 调 试 器 随 之 更 新 其 窗 口 , 以 提 醒 你 变 量 已 经 被 改变 。
值 得 一 提 的 是 , 无 论 什 么 时 候 , 当 程 序 存 取 一 个 变 量 时 , 也 就 是 说 , 当 它 从 变 量中 读 取 或 者 给 它 赋 值 时 , Intel 处 理 器 都 能 立 即 发 生 中 断 。 而 且 , Visual C++ 调 试器 不 会 利 用 这 种 选 项 , 因 为 通 常 情 况 下 , 与 进 行 调 试 时 的 赋 值 变 量 的 程 序 指 令 相比 , 读 取 变 量 的 程 序 指 令 更 缺 少 意 义 。用 处 理 器 的 调 试 寄 存 器 来 监 视 位 置 断 点 和
数 据 断 点 , 并 用 INT 3 指 令 来 保 存 覆 写 代 码 的 额 外 工 作 , 这 也 是 可 能 的 。 但 是 , 调 试 寄 存 器 是 珍 贵 的 资 源 , 因 此 , Visual C++ 调 试 器 通 过 调 用 中 断 3 来 监 视 位 置断 点 , 并 且 保 留 使 用 数 据 断 点 的 调 试 寄 存 器 。
通 过 使 用 调 试 寄 存 器 , 调 试 器 将 检 查 数 据 断 点 的 重 担 转 给 处 理 器 , 有 效 地 消 除 了性 能 障 碍 , 否 则 , 将 会 导 致 每 条 指 令 都 单 步 执 行 来 监 视 数 据 的 变 化 。 然 而 , 在 断点 包 括 条 件 时 使 用 数 据 断 点 ,例 如 变 量 x 是 否 超 出 了 某 个 值 ,数 据 断 点 也 可 能 影响 程 序 的 运 行 速 度 。 指 定 断 点 的 一 个 变 量 在 一 个 单 循 环 的 过 程 中 可 以 改 变 很 多次 。 变 量 每 变 化 一 次 , 处 理 器 就 会 中 断 , 控 制 权 返 回 调 试 器 , 然 后 估 价 条 件 。 永远 或 很 少 得 到 真 值 的 条 件 表 达 式 可 能 会 因 此 降 低 程 序 的 运 行 速 度 ,后 面 的 章 节 中将 会 谈 到 这 种 情 况 。 在 某 些 情 况 下 , 调 试 器 不 会 使 用 调 试 寄 存 器 来 监 视 数 据 断点 , 而 是 依 赖 于 速 度 更 慢 的 单 步 调 试 法 。
建 立 调 试 版 本
为 了 创 建 一 个 程 序 的 调 试 执 行 版 本 , 首 先 应 该 保 证 配 置 是 Win32 Debug 。 默 认 情况 下 , 当 你 创 建 一 个 新 项 目 时 , Visual C++ 即 设 置 配 置 为 Win32 Debug ,在 Build 工 具 栏 中 显 示 当 前 的 配 置 :
也 可 以 在 Build ( 建 立 ) 菜 单 中 单 击 Set Active Configuration( 设 置 活 动 配 置 ) 命令 , 以 查 看 当 前 的 配 置 , 看 是 否 有 必 要 将 它 更 改 为 调 试 版 本 。 W in32 Debug 配 置自 动 改 变 为 程 序 设 置 , 并 在 Project Settings( 项 目 设 置 ) 对 话 框 中 显 示 出 来 。 通过 单 击 Projec t( 项 目 ) 菜 单 中 的 Setting s( 设 置 ) 命 令 , 打 开 Project Setting s( 项目 设 置 ) 对 话 框 , 并 调 出 C/C++ 和 Link 选 项 卡 , 对 话 框 中 的 设 置 应 和 图 11-1 中所 示 类 似 :
- 在 C/C++ 选 项 卡 上 的 Optimizations ( 优 化 ) 组 合 框 显 示
Disable
( Debug )( 禁 用 ( 调 试 )) 选 项 。
- 在 Link 选 项 卡 中 , 复 选 标 志 显 示 在 Generate Debug Info( 生
成 调 试 信息 ) 复 选 框 中 。
有 了 这 些 设 置 , 就 可 以 顺 利 的 建 立 项 目 了 , 其 结 果 是 一 个 针 对 调 试 器 含 有 符 号 信息 的 程 序 调 试 版 本 。
C/C++ 选 项 卡 中 的 设 置
Link 选 项 卡 中 的 设 置
图 11-1 在 Project Settings 对 话 框 中 设 置 调 试 信 息
调 试 器 界 面
像 Visual C++ 编 辑 器 一 样 , 调 试 器 仅 适 用 于 Developer Studio 环 境 。 调 试 程 序 要求 项 目 是 开 放 式 的 , 并 且 , 你 应 该 创 建 一 个 程 序 的 调 试 执 行 版 本 。
文 本 编 辑 器 是 开 始 调 试 的 好 地 方 , 打 开 一 个 或 更 多 个 程 序 源 文 件 , 找 出 程 序 运 行时 需 中 断 执 行 的 行 , 单 击 此 行 所 在 的 位 置 , 将 在 此 处 设 置 插 入 标 志 , 然 后 按 F9 设 置 一 个 位 置 断 点 。编 辑 器 在 所 选 行 的 左 边 放 置 一 个 小 的 红 色 八 角 形 ,来 作 为 此行 的 标 记 , 以 表 示 中 断 。 如 果 禁 用 选 择 边 界 , 如 第 3 章 所 述 , 编 辑 器 会 将 整 行 用红 色 高 亮 显 示 。 为 了 取 消 位 置 断 点 ,可 在 要 设 置 位 置 断 点 的 所 在 行 的 任 一 位 置 设置 插 入 标 志 , 并 重 新 按 F9 来 消 除 断 点 。
如 果 想 使 用 鼠 标 而 不 是 键 盘 , 可 以 在 此 行 单 击 鼠 标 右 键 , 即 可 设 置 或 消 除 断 点 。如 图 11-2 的 上 下 文 菜 单 中 所 示 。 从 此 菜 单 中 , 可 选 择 Insert/Remove Breakpoint
( 插 入 /删 除 断 点 )命 令 来 清 除 或 设 置 断 点 。此 菜 单 也 提 供 一 个 D isable Breakpoint
( 禁 用 断 点 ) 命 令 , 允 许 关 闭 断 点 而 不 去 消 除 它 。
虽 然 有 点 不 太 方 便 , 但 也 可 以 通 过 Breakpoin t( 断 点 ) 对 话 框 来 设 置 一 个 位 置 断点 , 此 对 话 框 仅 提 供 设 置 数 据 断 点 的 方 式 , 和 两 个 其 他 变 量 , 称 为 条 件 断 点 和 消息 断 点 。
图 11-2 从 上 下 文 菜 单 中 选 择 Insert/Remove Breakpoin t( 插 入 /删 除 断 点 ) 命 令
Breakpoin t( 断 点 ) 对 话 框
图 11-3 所 示 为 Breakpoint 对 话 框 , 按 下 Ctrl+B 键 或 在 Edit ( 编 辑 ) 菜 单 中 单 击Breakpoint 命 令 , 对 话 框 中 的 三 个 选 项 卡 可 以 设 置 位 置 断 点 、 数 据 断 点 、 条 件 断点 和 消 息 断 点 , 后 面 介 绍 这 四 种 断 点 类 型 。
图 11-3 Breakpoin t( 断 点 ) 对 话 框
位 置 断 点
在 Breakpoint( 断 点 ) 对 话 框 中 设 置 位 置 断 点 比 按 F9 键 或 从 编 译 器 上 下 文 菜 单中 选 择 一 条 命 令 要 麻 烦 得 多 。 但 是 , 对 话 框 对 位 置 断 点 来 说 , 提 供 了 几 种 增 强 功能 , 这 些 被 证 明 是 非 常 有 用 的 。 你 可 以 在 Break A t( 断 点 位 置 ) 控 制 处 键 入 函 数名 , 以 便 在 函 数 的 第 一 行 处 设 置 一 个 位 置 断 点 ;键 入 标 号 的 名 字 在 标 志 行 处 设 置一 个 断 点 ( 在 这 二 者 之 间 有 一 点 细 微 的 差 别 , 因 为 函 数 名 仅 仅 是 一 个 标 号 )。 在Break At 控 制 中 的 字 母 必 须 与 函 数 名 或 标 号 相 符 合 , 并 且 , C++ 函 数 名 必 须 包 括类 名 和 作 用 域 解 析 运 算 符 。 因 此 , OnCreate 条 目 不 会 指 定 有 效 的 断 点 位 置 , 而CMainFrame::OnCreate 却 可 以 做 到 这 一 点 。
在 Breakpoint 对 话 框 中 键 入 函 数 或 标 号 名 , 以 便 在 源 文 件 中 设 置 一 个 有 效 的 断点 , 但 它 不 能 给 文 本 编 辑 器 提 供 行 号 。 因 为 文 本 编 辑 器 要 求 行 号 来 显 示 断 点 符号 , 所 以 , 它 不 能 在 文 档 窗 口 中 标 记 此 行 , 或 者 , 给 予 其 他 已 有 位 置 断 点 的 标 志行 一 些 别 的 可 视 线 索 。 在 Breakpoint 对 话 框 底 部 列 出 的 断 点 , 只 能 用 来 确 认 新 断点 的 存 在 ,断 点 符 号 的 停 止 标 志 仅 仅 在 调 试 器 处 于 运 行 状 态 时 的 源 文 件 中 显 示 出来 。
为 了 在 一 个 特 殊 行 中 设 置 位 置 断 点 ,在 紧 接 着 的 Break At 控 制 的 行 号 处 键 入 一 个句 点 。 对 于 当 前 行 , 也 就 是 说 , 在 文 本 编 辑 器 中 含 有 插 入 记 号 的 所 在 行 , 单 击 控件 右 边 的 小 箭 头 按 钮 , 并 选 择 所 给 的 行 号 , 这 个 按 钮 能 够 引 出 Advanced Breakpoin t( 高 级 断 点 ) 对 话 框 , 你 可 以 据 此 区 分 源 文 件 中 的 函 数 或 标 号 , 而 不
是 根 据 文 本 编 辑 器 中 显 示 的 内 容 。
位 置 断 点 有 类 似 于 文 本 编 辑 器 书 签 的 特 征 , 并 且 , Visual C++ 使 用 相 同 的 逻 辑 实现 断 点 和 书 签 。 除 了 标 记 特 殊 行 以 外 , 位 置 断 点 就 像 命 名 的 标 签 一 样 , 它 会 保 持在 文 档 中 的 固 定 位 置 , 除 非 你 消 除 它 。 如 果 在 Developer Studio 环 境 之 外 编 辑 了一 个 文 档 , 断 点 和 书 签 就 会 均 显 示 在 另 一 行 中 。在 调 试 器 处 于 运 行 状 态 的 环 境 下编 辑 文 档 时 , 也 能 与 此 位 置 之 外 的 位 置 断 点 发 生 冲 突 , 因 为 位 置 断 点 与 行 号 相 联系 , 随 着 文 档 在 编 辑 过 程 中 变 大 或 缩 小 , 新 的 语 句 可 能 会 进 入 标 有 断 点 的 行 号中 。 如 果 新 行 不 含 有 效 的 程 序 指 令 , Visual C++ 在 你 下 一 次 启 动 调 试 器 时 会 提 供警 告 :
当 出 现 这 种 消 息 时 , 应 该 使 用 文 本 编 辑 器 检 查 源 代 码 , 消 除 不 适 当 的 断 点 , 在 其原 来 的 位 置 上 重 新 设 置 新 的 断 点 。 如 果 你 经 常 在 Visual C++ 环 境 之 外 编 辑 源 代
码 , 或 者 在 调 试 器 处 于 运 行 状 态 时 进 行 一 些 变 化 , 以 期 望 能 不 断 地 了 解 一 些 信息 , 对 有 效 地 解 决 重 置 断 点 之 类 的 问 题 是 比 较 棘 手 的 , 尤 其 是 你 在 另 一 个 编 译 器中 做 了 某 些 变 换 时 , 就 像 书 签 、 断 点 是 指 针 , 而 不 是 嵌 入 源 代 码 文 档 中 的 字 符 一样 ( 仅 使 编 译 器 出 错 ), 因 此 , 改 变 该 环 境 外 的 文 本 将 不 可 避 免 地 有 重 置 指 针 目标 的 危 险 。
然 而 ,重 置 问 题 并 不 会 影 响 使 用 Breakpoint 对 话 框 时 与 符 号 或 函 数 名 相 联 系 的 位置 断 点 , 无 论 文 档 内 容 怎 么 改 变 , 这 些 位 置 断 点 对 其 标 志 来 说 总 是 保 持 固 定 , 因为 它 每 运 行 一 次 ,调 试 器 就 会 搜 寻 标 志 的 源 代 码 ,用 断 点 给 每 一 个 标 志 行 作 上 标记 , 这 经 常 是 在 使 用 Breakpoint 对 话 框 时 设 置 位 置 断 点 的 原 因 , 远 没 有 按 F9 设置 方 便 。在 以 后 的 章 节 中 ,我 们 将 使 用 对 话 的 方 式 在 一 个 调 试 例 子 中 来 设 置 位 置断 点 。
数 据 断 点
Breakpoint 对 话 框 仅 仅 提 供 了 一 种 设 置 数 据 断 点 的 方 法 , 当 变 量 的 值 有 所 改 变 , 或 者 , 当 条 件 表 达 式 变 成 真 时 , 数 据 断 点 即 被 触 发 。 如 果 已 经 使 用 了 微 软 的 旧 式CodeView 调 试 器 , 你 可 能 会 将 数 据 断 点 作 为 CodeView 中 的 跟 踪 点 。 单 击Breakpoint 对 话 框 中 的 Data ( 数 据 ) 选 项 卡 , 键 入 你 想 利 用 调 试 器 进 行 监 视 的 变量 或 表 达 式 的 名 称 ( 参 见 图 11-4 ), 进 入 一 个 标 准 的 C/C++ 条 件 表 达 式 , 例 如i==100 或 nCoun t> 25 。
当 调 试 器 处 于 运 行 状 态 时 , 可 以 为 不 在 作 用 域 中 的 变 量 设 置 数 据 断 点 ,方 法 是 先
在 Breakpoi n(t 断 点 )对 话 框 中 键 入 表 达 式 ,然 后 单 击 标 有 Enter The Expression To
Be Evaluated ( 输 入 要 求 值 的 表 达 式 ) 文 本 框 右 边 的 箭 头 按 钮 。 单 击 弹 出 的Advance d( 高 级 ) 命 令 , 并 输 入 所 要 求 的 文 本 信 息 , 使 调 试 器 跟 踪 进 入 作 用 域 的变 量 。
图 11-4 在 Breakpoin t( 断 点 ) 对 话 框 中 输 入 数 据 断 点
调 试 器 能 够 监 视 指 针 所 标 识 的 变 量 范 围 ,如 数 组 或 结 构 名 ,以 提 供 表 达 式 中 指 针的 重 引 用 状 况 。 例 如 , 在 Enter The Expression ( 输 入 表 达 式 ) 文 本 框 中 键 入 数 组名 iArray , 就 不 可 能 按 你 所 期 望 的 那 样 为 数 组 的 第 一 个 元 素 设 置 断 点 , 必 须 通 过键 入 iArray[0] 来 重 引 用 数 组 指 针 。 为 了 监 视 数 组 的 其 他 元 素 , 需 在 Enter The Number Of Elements To Watch 的 小 控 件 中 设 置 元 素 数 。 值 得 注 意 的 是 , 这 是 元 素数 , 而 不 是 字 节 数 。例 如 ,如果 iArray 含 有 整 数 , 在 第 一 个 控 件 中 键 入 iArray[0] 和 在 第 二 个 控 件 中 键 入 数 字 10 ,如 果 变 化 发 生 在 数 组 的 前 40 个 字 节( 从 iA rray[9] 到 iArray[0] 的 整 数 ), 就 会 引 起 程 序 中 断 。
类 似 地 , 为 了 监 视 变 量 pString 所 指 向 的 一 串 字 符 , 在 Enter The Expression 控 件中 键 入 * pString 。 在 较 小 的 控 件 中 , 键 入 你 想 利 用 调 试 器 监 视 的 字 节 数 , 键 入 不带 星 号 的 pString 重 引 用 运 算 符 意 味 着 , 只 有 在 pString 改 变 为 指 向 别 的 地 方 时 , 再 触 发 断 点 。 在 这 种 情 况 下 , 调 试 器 所 监 视 的 是 pString 本 身 , 而 不 是 它 所 指 向的 数 组 的 内 容 。
以 前 提 到 过 , 当 用 数 据 断 点 调 试 时 , 程 序 的 执 行 速 度 将 会 明 显 减 慢 。 当 设 置 的 断点 比 处 理 器 中 的 调 试 寄 存 器 所 能 容 纳 的 断 点 数 要 多 ,或 者 用 自 动 存 储 类 为 变 量 设置 了 数 据 断 点 时 ,程 序 运 行 速 度 也 会 降 下 来 。 自 动 数 据 包 括 函 数 自 变 量 以 及 在 没有 static 关 键 字 的 函 数 中 所 定 义 的 变 量 。 这 类 数 据 存 在 于 堆 栈 中 , 当 程 序 运 行 时就 不 再 存 在 。尽 管 你 可 以 为 自 动 变 量 设 置 数 据 断 点 , 但 调 试 器 也 不 会 利 用 处 理 器的 调 试 寄 存 器 来 监 视 断 点 , 因 此 , 当 变 量 在 作 用 域 内 时 , 执 行 速 度 就 会 下 降 。 调试 器 利 用 调 试 寄 存 器 监 视 的 仅 仅 是 存 在 于 程 序 数 据 段 中 的 静 态 变 量 的 数 据 断
点 , 而 不 是 堆 栈 中 的 局 部 数 据 。
由 于 数 据 断 点 会 使 程 序 的 执 行 速 度 大 大 降 低 , 可 能 会 使 你 认 为 程 序 已 被 挂 起 了 。使 用 数 据 断 点 时 ,需 要 耐 心 。如 果 你 认 为 程 序 确 实 因 为 某 些 原 因 停 止 了 ,在 Visual C++ Debug ( 调 试 ) 菜 单 中 单 击 Break ( 中 断 ) 命 令 , 这 就 会 中 断 程 序 , 并 将 控制 权 返 回 给 调 试 器 。
条 件 断 点
条 件 断 点 是 位 置 断 点 的 扩 展 方 式 ,在 源 代 码 指 令 中 设 置 断 点 与 设 置 位 置 断 点 的 方式 相 同 。 但 是 , 当 控 制 到 达 带 标 记 的 指 令 时 , 如 果 指 定 的 条 件 是 真 , 调 试 器 就 会响 应 条 件 断 点 。 条 件 断 点 在 同 一 指 令 执 行 数 百 次 的 循 环 中 用 处 不 大 ,设 置 在 循 环中 的 位 置 断 点 在 每 次 重 复 过 程 中 都 会 停 止 执 行 , 这 是 我 们 所 不 希 望 的 。条 件 断 点中 断 指 令 仅 在 某 些 情 况 下 发 生 , 例 如 当 循 环 次 数 达 到 100 时 。
在 Breakpoin t( 断 点 ) 对 话 框 的 Location ( 位 置 ) 选 项 卡 中 设 置 条 件 断 点 , 在 定义 想 利 用 断 点 标 记 的 源 代 码 后 , 单 击 图 11-3 中 所 示 的 Condition ( 条 件 ) 按 钮 , 显 示 Breakpoint Condition 对 话 框 :
在 对 话 框 的 顶 部 控 件 中 ,以 C/C++ 条 件 表 达 式 的 形 式 键 入 断 点 条 件 。所标记的指令 每 运 行 一 次 , 如 果 表 达 式 是 真 或 非 零 , 调 试 器 就 会 对 表 达 式 进 行 一 次 求 值 , 并中 断 程 序 流 。 Breakpoint Condition ( 断 点 条 件 ) 对 话 框 中 的 底 部 文 本 框 允 许 你 定义 在 调 试 器 中 断 程 序 之 前 条 件 必 须 为 真 的 次 数 。
消 息 断 点
消 息 断 点 与 窗 口 过 程 紧 密 相 关 , 当 窗 口 过 程 接 收 到 一 条 定 义 的 信 息 时 , 例 如WM_SIZE 或 W M _ C O M M A N D , 便 执 行 中 断 。 消 息 断 点 在 使 用 MFC 的 C/C++ 程 序 中 无 多 大 用 处 。 因 为 窗 口 过 程 通 常 存 在 于 MFC 框 架 中 , 而 不 是 在 程 序 源 代码 中 。 为 了 中 断 MFC 程 序 中 的 特 定 信 息 , 必 须 为 处 理 消 息 的 函 数 设 置 一 个 位 置断 点 , 在 类 的 消 息 映 射 中 将 会 被 标 识 。
图 11-5 将 描 述 怎 样 为 ButtonProc 窗 口 过 程 设 置 消 息 断 点 , 范 例 如 下 :
int CALLBACK ButtonProc (HWND hwnd,UINT msg,
WPARAM wParam, LPARM lParam);
图 11-5 在 Breakpoin t( 断 点 ) 对 话 框 中 设 置 消 息 断 点
当 操 作 系 统 调 用 ButtonProc 过 程 时 , 它 会 在 msg 参 数 中 传 递 诸 如W M _ C O M M A N D 或 W M _CREATE 的 消 息 值 , 以 通 知 程 序 为 何 被 调 用 。 当ButtonProc 接 收 到 一 条 特 定 的 消 息 时 , 为 了 中 断 运 行 , 单 击 Breakpoin t( 断 点 ) 对 话 框 中 的 Message ( 消 息 ) 选 项 卡 , 并 在 Break At WndProc 控 件 中 键 入ButtonProc, 单 击 第 二 个 组 合 框 中 的 箭 头 , 显 示 出 消 息 标 识 符 的 下 拉 列 表 , 并 选择 一 条 消 息 , 诸 如 在 图 11- 5 中 所 示 的 WM_CREATE 消 息 。 当 运 行 调 试 器 中 的程 序 时 ,在 W indows 调 用 带 有 WM_CREATE 消息的过程时,在 ButtonProc 的 第一 行 处 将 中 断 执 行 。
可 以 看 到 , 消 息 断 点 就 是 条 件 断 点 的 特 殊 形 式 , 在 Location( 位 置 ) 选 项 卡 中 可以 获 得 相 同 的 结 果 , 方 法 是 对 具 有 如 下 条 件 的 ButtonProc 标 号 设 置 位 置 断 点 :
msg== WM_CREATE
运 行 调 试 器
- 旦 确 定 了 程 序 停 止 的 条 件 和 位 置 , 就 作 好 了 运 行 的 准
备 。 现 在 , 是 文 本 编 辑 器而 不 是 调 试 器 处 于 运 行 状 态 。 执 行 程 序 的 调 试 版 本 , 只 要 启 动 调 试 器 , 接 着 运 行程 序 。
从 Build( 建 立 ) 菜 单 中 选 择 Start Debug ( 开 始 调 试 ) 命 令 , 将 出 现 四 个 名 字 为
G o ( 转 到 )、 Step Into ( 开 始 内 部 单 步 调 试 )、 Run To Curso r( 运 行 到 光 标 处 ) 和
A ttach To Proces s( 与 进 程 联 系 ) 的 选 项 , 如 图 11 -6 所 示 。 在 源 代 码 中 设 置 了 至少 一 个 断 点 时 , 即 可 以 使 用 G o 命 令 。 调 试 器 运 行 程 序 时 , 当 程 序 中 的 执 行 流 到达 位 置 断 点 或 触 发 数 据 断 点 时 , 就 会 停 下 。 顾 名 思 义 , Step Into 命 令 的 意 思 是 : 进 入 程 序 , 并 且 在 第 一 条 指 令 处 终 止 。 W indows 程 序 的 第 一 条 指 令 是 W inMain 函 数 的 开 始 处 , 或 者 , 对 于 MFC 程 序 , 是 _tWinMain 函 数 的 开 始 处 。 在 另 外 一种 情 况 下 , 调 试 器 打 开 源 模 块 , 对 _tWinMain 来 说 , 是 位 于 MFC 文 件 夹 中 的Appmodul.cpp 文 件 , 并 显 示 在 源 文 件 的 窗 口 中 。
图 11-6 从 Build (建立)菜单中启动调试器
Run To Cursor 命 令 在 插 入 标 记 符 号 的 源 代 码 行 处 终 止 执 行 , 如 果 在 文 本 编 辑 器中 没 有 源 文 件 打 开 , Run To Cursor 命 令 将 会 失 去 作 用 。 否 则 , 它 将 提 供 较 方 便的 方 式 进 入 程 序 中 ,而 无 须 设 置 一 个 断 点 , 如 果 程 序 流 在 到 达 插 入 记 号 之 前 触 发了 断 点 , 在 断 点 处 , 执 行 程 序 就 会 停 下 , 而 不 是 在 带 有 插 入 记 号 的 行 。 为 了 继 续
运 行 程 序 , 在 目 标 行 处 重 新 设 置 插 入 记 号 , 再 次 单 击 Run To Cursor。利 用 A ttach To Process( 与 进 程 关 联 ) 命 令 , 可 启 动 调 试 器 , 并 且 可 将 它 与 目 前 运 行 的 程 序联 系 起 来 。 调 试 器 通 过 DeugActiveProcess API 函 数 来 完 成 这 种 功 能 , 在 在 线 帮助 中 有 所 描 述 。
调 试 器 为 Start Debu g( 开 始 调 试 ) 中 的 前 三 个 子 命 令 提 供 了 快 捷 键 , 因 此 , 可 以不 打 开 Build ( 建 立 ) 菜 单 来 开 始 调 试 。 快 捷 键 F5 的 功 能 是 G o ( 转 到 ), F11 快捷 键 的 功 能 是 Step Into ( 开 始 内 部 单 步 调 试 )。 Ctrl+F10 快 捷 键 的 功 能 是 Run To Curso r。
调 试 器 窗 口
调 试 的 程 序 在 某 一 断 点 处 停 止 时 , 调 试 器 更 新 带 有 目 前 程 序 运 行 状 态 消 息 的 窗口 。 也 许 调 试 器 窗 口 中 最 重 要 的 是 源 程 序 窗 口 , 它 能 显 示 程 序 所 停 之 处 的 源 代码 。 称 为 指 令 指 针 的 小 黄 色 箭 头 出 现 在 中 断 指 令 左 边 的 选 择 边 界 处( 如 果 选 择 边界 禁 用 , 整 行 将 会 以 黄 色 高 亮 显 示 )。 这 种 标 志 表 明 程 序 恢 复 运 行 时 , 将 要 执 行而 还 没 有 执 行 的 下 一 行 。
如 图 11-7 所 示 ,当 调 试 器 重 新 获 取 控 件 时 ,Debug( 调 试 器 )工 具 栏 出 现 在 屏 幕 上 。图 中 标 有 Debugger Windows 的 六 个 按 钮 充 当 触 发 器 , 显 示 或 隐 藏 含 有 目 前 程 序状态消息的窗口。表 11- 1 描 述 了 显 示 在 每 个 窗 口 中 的 消 息 类 型 。 Debug( 调 试 器 ) 工 具 栏 对 源 程 序 窗 口 来 说 不 含 有 类 似 的 按 钮 ,因 为 调 试 器 仅 仅 从 文 本 编 辑 器 中 借
用 窗 口 。 当 你 需 要 查 看 普 通 文 档 时 , 打 开 或 关 闭 源 程 序 窗 口 即 可 。
图 11-7 触 发 调 试 器 窗 口 开 关 的 工 具 按 钮
表 11-1 被 Debug( 调 试 器 ) 工 具 栏 中 的 按 钮 激 活 的 六 个 调 试 器 窗 口 中 的 消 息窗 口 按 钮 说 明
Watch(
观 察 ) 通 过 调 试 器 跟 踪 的 目 前 变 量 和 表 达 式的 值 。 在
Watch 窗 口 中 , 指 定 当 程 序被 挂 起 时 , 要 了 解 哪 些 变 量 的
当 前 值
Variables
( 变 量 ) 在 访 问 或 靠 近 中 断 位 置 处 的 当 前 变 量值 ,
Variable s( 变 量 ) 窗 口 具 有 以 下 三个 选 项 卡 :
Aut o( 自 动 ): 显 示 变 量 和 函 数 的 返 回值
Registers ( 寄 存器 )
续 表
Locals ( 局 部 ): 显 示 当 前 函 数 的 局 部变 量
This ( 当 前 ): 在 一 个 C++ 程 序 中 , 标识 指 针 目 前 所 指 向 的 目 标
CPU
寄 存 器 的 当 前 内 容
M
emory ( 内 存 ) 特 定 地 址 的 内 存 转 储
Call Stack ( 调 用堆 栈 )
D isassembly ( 反汇 编 )
续 表
没
有 返 回 的 被 调 用 函 数 的 列 表 , Call Stack ( 调 用 堆 栈 ) 显
示 通 过 嵌 套 函 数调 用 断 点 位 置 的 执 行 路 径
在
屏 幕 上 , 为 了 补 充 源 程 序 窗 口 , 其中 显 示 编 译 代 码 的 汇
编 语 言 转 换 , “ D isassembl y( 反 汇 编 )” 意 味 着 将 程序 中
的 机 器 码 转 化 为 相 当 的 汇 编 指 令
Watch 窗 口 提 供 了 特 定 变 量 的 概 况 , 显 示 出 当 程 序 挂 起 时 这 些 变 量 的 当 前 值 。Watch ( 观 察 ) 窗 口 的 变 量 与 中 断 程 序 流 没 有 关 系 , 因 此 , 不 要 将 观 察 到 的 变 量与 数 据 断 点 中 所 设 置 的 变 量 混 淆 。为了在 Watch 窗 口 增 加 一 个 变 量 ,在 窗 口 中 双击 带 点 的 新 项 目 框 , 并 键 入 变 量 名 。
图 11-7 中 所 示 的 QuickWatch ( 快 速 查 看 ) 工 具 栏 , 对 没 有 在 Watch 窗 口 中 添 加的 变 量 , 提 供 了 一 种 查 看 的 方 法 。 为 了 方 便 起 见 , 可 以 简 单 地 在 源 程 序 窗 口 中 将鼠 标 光 标 指 向 变 量 名 来 查 询 其 当 前 值 。这 将 在 含 有 当 前 值 的 工 具 提 示 窗 口 中 显 示出 来 :
对 诸 如 结 构 元 素 和 类 成 员 的 一 些 变 量 ,对 调 试 器 来 说 , 可 能 不 能 确 切 地 辨 别 这 些变 量 。 在 这 种 情 况 下 , 必 须 首 先 选 择 目 标 和 变 量 名 以 及 相 关 的 点 运 算 符 ( 同 在M yClass .M ember 中 的 一 样 ), 然 后 将 鼠 标 光 标 停 在 源 程 序 窗 口 的 选 择 项 上 。
在 Watch 窗 口 中 , 可 以 看 到 作 用 域 中 的 变 量 , 而 不 管 它 们 是 在 程 序 中 的 什 么 地 方获 取 的 。Variables 窗 口 将 集 中 在 运 行 的 冻 结 点 上 。 在 程 序 挂 起 之 前 , 最 终 运 行 的指 令 ( 可 能 还 有 前 一 两 条 指 令 ) 所 引 用 的 所 有 变 量 , 将 出 现 在 Variables 窗 口 中 。你 也 可 以 通 过 在 Variables 窗 口 中 双 击 变 量 , 并 键 入 新 值 , 来 改 变 它 的 值 。
Register s( 寄 存 器 ) 窗 口 一 般 仅 当 D isassembly( 反 汇 编 ) 窗 口 处 于 运 行 状 态 时 使用 , 当 程 序 被 挂 起 时 , 它 显 示 处 理 器 寄 存 器 的 状 态 。 在 表 11-2 中 所 描 述 的 Intel 处 理 器 标 记 就 是 位 标 记 ,以 及 双 击 Register s( 寄 存 器 )窗 口 中 的 标 记 所 触 发 的 值 。表 中 的 符 号 栏 显 示 出 现 在 Registers ( 寄 存 器 ) 窗 口 中 的 标 记 符 号 。
调 试 器 窗 口 能 够 存 储 许 多 信 息 , 但 在 一 般 情 况 下 , 不 必 在 同 一 时 间 内 查 看 它 们 。窗 口 还 具 有 显 示 源 程 序 窗 口 中 的 源 代 码 的 屏 幕 空 间 ,一 般 情 况 下 , 总 可 以 看 到 它们 , 以 便 你 知 道 当 时 正 处 于 程 序 中 的 什 么 地 方 。
表 11-2 Intel 处 理 器 标 记
标 记 名 符 号 说 明
Overflow ( 溢 出 ) O V 当 一 个 整 数 指 令 所 产 生 的 结 果 太 小 或 者
太 大 , 不 能 填 入 目 标 寄 存 器 或 内 存 地 址 中时 , 便 设 置 此 标 记
D irection( 方 向 ) U P 决 定 重 复 字 符 串 和 比 较 指 令 的 方 向 , 例 如
M O V S( Move String)和 CMP S( Compare String )
Enable interrupt ( 启用 中 断 )
EI 当 清 除 它 时 , 处 理 器 忽 略 硬 件 中 断 , 如 键盘 活 动
续 表
Sign ( 正 负 号 ) PL 含 有 一 条 算 术 指 令 的 高 序 位 值( 不 清 楚 为
什 么 Visual C++ 设 计 者 选 择 字 母 PL 来 代表 符 号 标 志 , 不 要 将 它 与 处 理 器 的 I/O Privilege Level 标 志 相 混 淆 )
Zero ( 零 ) ZR 当 一 条 算 术 指 令 的 结 果 是 零 时 设 置
Auxiliary carr y( 附 加进 位 )
A C 在 一 条 算 术 指 令 后 , 含 有 A L 寄 存 器 的 四个 低 序 位 ( 也 叫 做 半 字 节 ) 的 进 位
Parity ( 奇 偶 性 ) PE 当 一 条 算 术 指 令 结 果 的 二 进 制 值 是 偶 数
个 1 位 时 设 置
Carry ( 进 位 ) CY 类 似 于 Overflow 标 记 ,但 指 示 无 符 号 的 溢
出 , 可 以 通 过 STC ( Set Carry ) 和 CLC
( Clear Carry ) 指 令 来 控 制
如 第 一 章 所 提 到 的 , 调 试 器 使 用 环 境 Output ( 输 出 ) 窗 口 来 显 示 来 自OutputDebugString 函 数 或 afxDump 类 的 数 据 。 Outpu t( 输 出 ) 窗 口 也 能 显 示 线程 终 止 代 码 、 第 一 次 异 常 通 知 , 并 调 出 信 息 。 在 Debug 工 具 栏 中 , 没 有 控 制 显
示 Output 窗 口 的 按 钮 , 因 为 此 窗 口 属 于 Developer Studio 环 境 , 而 不 是 调 试 器 。通 过 单 击 标 准 工 具 栏 上 的 Output 按 钮 , 将 显 示 或 隐 藏 Output 窗 口 。
只 有 真 正 使 用 调 试 器 窗 口 , 才 能 掌 握 它 。 在 以 后 的 章 节 中 , 将 会 给 出 一 个 例 子 来描 述 调 试 器 窗 口 怎 样 调 试 进 程 。
单 步 调 试 程 序
当 利 用 调 试 器 跟 踪 一 个 程 序 的 错 误 时 ,应 当 确 定 你 认 为 有 问 题 的 代 码 段 , 并 在 此段 或 刚 好 在 此 段 之 前 设 置 一 个 位 置 断 点 。当 调 试 器 在 断 点 处 将 程 序 挂 起 时 , 你 可以 单 步 调 试 程 序 , 每 次 执 行 一 条 指 令 , 检 查 变 化 时 的 变 量 状 况 。
如 图 11-8 所 示 , Debug 工 具 栏 拥 有 一 组 四 个 按 钮 , 可 以 让 你 单 步 调 试 被 挂 起 的程 序 。 可 以 通 过 箭 头 粗 略 地 了 解 Step 工 具 ; 现 在 我 们 知 道 , 按 钮 上 的 图 像 表 达了 它 的 作 用 , 在 如 下 次 序 所 显 示 的 按 钮 中 , 分 别 激 活 Step Int o( 开 始 内 部 单 步 调
试 ),Step Ove r( 整 体 单 步 调 试 ), Step O u (t 完 成 内 部 单 步 调 试 )和 Run To Cursor
( 运 行 到 光 标 处 ) 命 令 , 在 这 之 前 , 我 们 已 经 讨 论 了 Run To Curso r( 运 行 到 光标 处 ) 命 令 , 其 他 的 三 条 命 令 则 需 要 更 多 的 解 释 。
图 11-8 Debug( 调 试 器 ) 工 具 栏 中 的 四 个 Step (单步调试)工具
利 用 Step Into 和 Step Over 命 令( 或 者 它 们 等 同 的 快 捷 键 F11 和 F10 ),可 以 单 步调 试 程 序 。 选 择 Step Into 或 Step Over 命 令 时 , 调 试 器 可 以 让 程 序 恢 复 运 行 , 但仅 仅 执 行 一 条 指 令 。 当 指 令 完 成 时 , 调 试 器 重 新 将 执 行 程 序 挂 起 。 你 可 能 会 想 , 是 什 么 组 成 了 一 条 指 令 , 因 为 在 高 级 语 言 , 像 C++ 中 , 一 条 简 单 的 命 令 可 以 转 化成 12 条 处 理 器 级 别 的 机 器 码 指 令 。 它 取 决 于 是 否 启 用 了 D isassembly ( 反 汇 编 ) 视 图 , 如 果 启 用 了 , 则 单 步 操 作 的 指 令 仅 仅 是 当 前 的 机 器 码 指 令 , 移 动 箭 头 将 指向 反 汇 编 列 表 中 的 另 一 条 指 令 。如 果 没 有 启 用 反 汇 编 视 图 ,Step Into 或 Step Over 命 令 执 行 当 前 的 源 程 序 窗 口 中 的 C/C++ 指 令 , 处 理 由 一 组 机 器 码 构 成 的 指 令 。
当 Step Into 和 Step Over 命 令 作 为 调 用 函 数 指 令 使 用 时 , 你 会 对 它 们 有 更 深 的 认识 。 思 考 一 下 , 当 调 试 器 在 如 下 的 if 语 句 中 终 止 程 序 运 行 时 会 发 生 什 么 :
if (Function1(hdc, Function2(msg))) x=3;
else
y=100;
Step Over 命 令 所 执 行 的 如 其 名 字 所 指 的 一 样 , 处 理 整 个 if 语 句 , 包 括 调 用Function1 和 Function2 。 程 序 在 x=3 或 y=100 语 句 处 会 再 次 终 止 。 Step Into 命 令处 理 这 种 情 况 稍 有 不 同 。 当 你 在 if 语 句 中 单 击 Step Into 按 钮 时 , 调 试 器 进 入Function2 , 并 在 第 一 条 指 令 处 停 下 。 如 果 此 时 检 查 Call Stac k( 调 用 堆 栈 ) 窗 口 , 将 会 在 此 列 表 的 顶 部 看 到 Function2 , 并 在 下 部 看 到 你 刚 离 开 的 函 数 名 。
此 时 , Step Ou 命 令 就 有 用 了 , 这 个 命 令 执 行 当 前 函 数 的 其 余 部 分 , 接 着 , 在 所调 用 函 数 之 后 的 下 一 条 语 句 处 停 下 来 。 换 句 话 说 , 所 调 用 的 Step Into 和 Step Out 命 令 一 起 作 用 于 一 个 函 数 时 , 它 有 着 与 Step Over 相 同 的 作 用 。 而 且 , 如 果 指 令在 所 给 的 例 子 中 含 有 不 止 一 个 函 数 , 事 情 将 会 更 复 杂 。 在 条 件 语 句 中 单 击 Step Into 按 钮 , 并 在 Function2 的 第 一 条 指 令 处 终 止 时 , 继 续 单 击 Step Out 命 令 , 指令 指 针 的 箭 头 保 持 指 向 if 语 句 。这 是 因 为 Function2 已经完成运行,但 Function1 还 没 有 被 调 用 , 再 次 激 活 Step Into 指向 Function1 的 第 一 条 指 令 。 如 果 单 击 Step Out 命 令 返 回 Function1 , 指 令 箭 头 仍 然 指 向 if 语 句 , 因 为 if 语 句 检 测 自 身 还 没有 执 行 。
利 用 反 汇 编 的 代 码 视 图 , 可 以 对 所 发 生 的 一 切 更 清 楚 , 带 阴 影 的 行 指 示 C 源 程序 语 句 ,紧 跟 的 是 等 价 的 反 汇 编 指 令( 反 汇 编 行 仅 仅 用 来 阐 述 if 语 句 中 的 内 部 事件 链 ,并不意味着 D isassembl y( 反 汇 编 ) 窗 口 是 可 视 的 )。在 代 码 序 列 的 开 始 处 , 黄 色 的 指 针 箭 头 指 向 if 语 句 :
if (Function1(hdc, Function2( msg )))
mov eax, dword ptr [msg] push eax
mov ecx, dword ptr [this]
call @ILT+30(CMyClass::Function2) (004010le) push eax
mov eax, dword ptr [hdc]
push eax
mov ecx, dword ptr [this]
call @ILT+85(CMyClass::Function1) (00401055)
test eax, eax
je CMyClass::Caller+00000071
X = 3;
mov dword ptr [x], 00000003 else
jmp CMyClass::Caller+00000078 y = 100;
Mov dword ptr [y], 00000064
W indows 95 不 允 许 进 入 系 统 API 函 数 进 行 单 步 调 试 , 如 下 所 示 :
::SlectObject( hdc,::GetStockObject(BLACK_PEN));
如 果 在 这 条 指 令 中 选 择 Step Into , SlectObject 和 GetStockObject 在 调 试 器 停 止 运行 之 前 继 续 执 行 下 一 语 句 。 在 这 种 情 况 下 , Step Into 和 Step Over 具 有 相 同 的 作用 。
终 止 和 重 新 启 动 调 试 器
如 图 11-9 中 所 示 , Restart ( 重 新 启 动 ) 按 钮 可 以 取 消 执 行 命 令 , 并 从 头 开 始 重新 运 行 程 序 , 废 弃 诸 如 系 统 资 源 或 内 存 之 类 的 分 配 。 这 可 以 了 结 以 前 的 状 态 , 而无 须 退 出 并 重 新 启 动 调 试 器 。 单 击 Stop Debugging( 停 止 调 试 ) 按 钮 , 可 立 即 退出 调 试 器 , 一 步 取 消 调 试 器 和 程 序 。 Break Execution 按 钮 同 Debu g( 调 试 ) 菜 单中 的 Break ( 中 断 ) 指 令 一 样 , 有 着 同 样 的 作 用 , 终 止 程 序 的 运 行 , 并 将 控 制 权返 回 给 调 试 器 。选 择 Break Executio n( 中 断 执 行 )按 钮 来 终 止 无 限 循 环 中 的 程 序 。
图 11-9 控制执行点的调试器工具
标 有 Show Next Statemen t( 显 示 下 一 语 句 ) 的 小 黄 色 箭 头 可 以 很 好 地 解 决 以 前 的问 题 , 当 程 序 被 挂 起 时 跟 踪 错 误 , 经 常 要 求 查 询 其 他 部 分 的 文 档 , 并 且 , 甚 至 会
使 你 进 入 别 的 源 文 件 中 。单 击 Debug( 调 试 器 )工 具 栏 中 的 Show Next Stateme n(t 显
示 下 一 语 句 ) 箭 头 , 会 马 上 带 你 返 回 到 源 程 序 窗 口 中 的 终 止 指 令 处 。 从 这 个 工 具图 标 中 , 可 以 联 想 到 出 现 在 邻 近 终 止 指 令 选 择 边 界 中 的 指 令 指 针 箭 头 。
随 机 校 正
在 调 试 程 序 时 发 现 了 错 误 时 , Visual C++ 在 许 多 情 况 下 允 许 你 不 停 止 调 试 器 而 继续 进 行 校 正 。 例 如 , 一 个 变 量 含 有 一 个 错 误 的 值 , 在 Variables ( 变 量 ) 窗 口 中 , 键 入 正 确 的 值 , 很 容 易 就 可 以 继 续 执 行 程 序 。 如 果 喜 欢 从 一 条 新 指 令 中 恢 复 运 行程 序 , 右 击 源 程 序 窗 口 需 要 的 行 , 从 上 下 文 菜 单 中 选 择 Set Next Statemen t( 设 置下 一 条 语 句 ) 命 令 :
这 将 会 使 指 令 指 针 重 新 再 次 指 向 单 击 过 的 行 , 允 许 你 重 做 或 跳 过 程 序 中 的 指 令 。而 且 , 对 你 来 说 , 这 是 一 个 负 担 , 你 要 保 证 移 动 的 指 令 指 针 方 向 不 要 错 。 重 复 赋
值 指 令 通 常 来 说 是 比 较 安 全 的 , 像 许 多 调 用 的 API 函 数 一 样 , 但 是 , 在 重 新 执 行一 条 new 指 令 或 分 配 资 源 的 其 他 代 码 的 情 况 下 , 一 定 要 当 心 。
在 调 试 会 话 中 键 入 一 个 新 值 , 这 是 比 较 好 的 临 时 处 理 办 法 。 它 意 味 着 , 最 终 你 将退 出 调 试 器 ,使 用 文 本 编 辑 器 修 整 错 误 的 源 代 码 ,并 重 新 编 译 ,得 到 正 确 的 结 果 。但 Visual C++ 现 在 提 供 了 一 个 更 方 便 的 方 法 , 对 那 些 运 行 过 的 步 骤 , 你 可 以 在 调试 器 的 源 窗 口 修 改 出 现 的 问 题 , 而 且 这 种 修 改 是 永 久 性 的 。 在 编 辑 完 源 程 序 后 , 重 新 恢 复 运 行 程 序 时 , Visual C++ 首 先 编 译 已 校 正 的 代 码 , 并 用 正 确 的 版 本 替 代被 影 响 的 机 器 指 令 。 这 种 修 改 是 永 久 性 的 , 尽 管 你 关 闭 了 调 试 器 , 重 新 编 译 被 校正 的 源 代 码 , 并 再 次 启 动 调 试 器 。
Edit and Continue( 编 辑 和 继 续 执 行 ) 特 征 是 众 所 周 知 的 , 在 调 试 程 序 时 , 总 是使 用 它 们 。 尽 管 你 可 以 选 择 通 过 手 工 或 者 自 动 应 用 这 些 特 征 。 默 认 情 况 下 , 当 你选 择 G o ( 转 到 ) 或 Step 命 令 中 的 一 条 命 令 , 在 修 正 源 代 码 后 , 继 续 执 行 被 中 断的 程 序 时 , 它 们 自 动 处 于 激 活 状 态 。如 果 你 倾 向 于 更 多 地 控 制 调 试 会 话 中 变 换 的代 码 是 否 重 新 编 译 , 则 显 示 Options( 选 项 ) 对 话 框 中 的 Debug 选 项 卡 , 清 除 标有 Debug Commands Invoke Edit And Continue ( 调 试 命 令 调 用 编 辑 和 继 续 执 行 ) 的 复 选 框 。这 并 不 能 使 编 辑 和 继 续 执 行 特 征 失 去 作 用 , 但 仅 能 保 证 调 试 器 在 你 没有 允 许 时 不 会 去 调 用 它 们 。 只 要 需 要 , 就 可 以 在 调 试 器 中 重 新 编 译 校 正 的 代 码 , 方 法 是 单 击 Debug( 调 试 器 ) 工 具 栏( 图 11-10 )上的 Apply Code Change s( 应 用 代码 更 改 ) 按 钮 , 或 者 从 Debu g( 调 试 ) 菜 单 或 源 程 序 窗 口 的 上 下 文 菜 单 中 选 择 相应 的 命 令 。 Apply Code Changes 命 令 仅 当 你 对 代 码 做 某 些 更 改 时 才 会 起 作 用 , 所
做 的 更 改 或 者 在 调 试 器 的 源 程 序 窗 口 中 ,或 在 Visual C ++ 环 境 之 外 的 文 本 编 辑 器中 。
重 新 编 译 代 码 后 , 执 行 点 有 时 会 有 所 改 变 , 在 这 种 情 况 下 , 调 试 器 会 用 消 息 来 通知 你 。 如 果 指 令 指 针 不 在 它 应 该 在 的 地 方 , 应 该 用 Set Next Statement 命 令 重 新进 行 设 置 。
图 11-10 调用调试器的 Edit 和 Continue 特 性
应 当 记 住 , Edit and Continue 命 令 有 某 些 限 制 。 这 种 特 征 不 能 识 别 源 程 序 中 不 可能 、 不 切 实 际 的 源 代 码 更 改 , 也 不 能 识 别 在 调 试 时 进 行 编 译 不 安 全 的 代 码 更 改 , 例 如 :
-
某 些 异 常 处 理 程 序 的 改 变 。
-
函 数 的 大 量 删 除 。
-
类 和 函 数 定 义 的 变 化 。
-
静 态 函 数 的 变 化 。
-
项 目 RC 文 档 中 的 源 程 序 数 据 的 变 化 。
完 成 上 述 任 何 变 化 后 , 要 通 过 Edit and Continue 命 令 来 恢 复 程 序 运 行 , 调 试 器 会在 状 态 栏 中 显 示 一 条 错 误 消 息 来 解 释 问 题 所 在 。你 有 权 选 择 是 使 用 以 前 的 代 码 继续 调 试 , 还 是 关 闭 调 试 器 , 并 重 新 编 译 校 正 后 的 代 码 。 为 了 安 全 起 见 , Edit and Continue 命 令 可 用 来 延 迟 对 一 个 函 数 的 更 改 , 直 到 所 调 用 的 堆 栈 完 全 “ 展 开 ”。如 果 你 编 辑 在 执 行 时 被 冻 结 的 函 数 , 并 且 , 如 果 函 数 已 被 另 一 个 函 数 调 用 , 调 试器 在 你 试 着 恢 复 运 行 时 , 将 显 示 下 图 所 示 的 消 息 。
这 个 对 话 框 并 不 是 一 条 错 误 消 息 , 它 仅 仅 提 醒 你 ,函 数 将 要 利 用 其 初 始 代 码 来 完成 运 行 , 并 且 , 你 的 校 正 不 会 起 任 何 作 用 , 直 到 下 一 次 函 数 执 行 相 同 的 调 用 链 。单 击 Yes 按 钮 继 续 运 行 , 单 击 N o 来 取 消 Edit and Continue 命 令 , 并 返 回 调 试 器 。
断 点 编 程
有 时 ,做 一 些 小 的 确 认 测 验 来 通 过 程 序 , 比 在 不 同 的 位 置 设 置 断 点 要 容 易 方 便 得多 。 ASSERT 宏 一 般 用 做 这 样 的 目 的 , 如 果 产 生 错 误 , 即 终 止 运 行 , 通 过 创 建 一个 简 单 的 名 为 CHECK 的 测 试 宏 来 编 制 自 己 的 断 点 , 以 显 示 一 条 消 息 , 如 果 测 试失 败 , 则 终 止 执 行 。 断 点 在 宏 中 容 易 编 制 , 利 用 在 线 汇 编 , 可 以 简 单 地 将 其 编 码成 一 条 INT 3 指 令 。 在 以 前 的 章 节 中 , 我 们 已 经 谈 过 , 在 处 理 器 执 行 它 时 , 这 个指 令 会 将 控 制 权 返 回 给 调 试 器 。
只 有 在 一 个 条 件 失 败 时 , CHECK 宏 便 触 发 INT 3 断 点 。 宏 采 用 两 个 参 数 : 一 个表 达 式 用 来 检 测 条 件 , 还 有 一 个 用 来 解 释 错 误 的 字 符 串 指 针 。
#ifdef_DEBUG
#define CHECK(b, s) \ if (b) \
{ \
::MessageBox(NULL, s, "CHECK Error"), MB_ICONINFORMATION); \
_asm int 3 \
}
#else
#define CHECK(b, s)
#endif
利 用 此 处 的 宏 , 可 以 检 测 程 序 的 逻 辑 性 , 如 :
iRet = Function1() ;
CHECK(iRet !=1, "Bad return value from Function1");
如 果 检 测 失 败 , 宏 会 显 示 错 误 消 息 , 并 在 断 点 处 终 止 程 序 的 运 行 , 重 新 设 置 指 令指 针 。 接 着 , 你 可 以 更 正 调 试 器 中 出 现 的 问 题 , 重 新 设 置 指 令 指 针 , 再 次 通 过Edit and Continue 命 令 试 着 运 行 该 指 令 。
例 子 : 开 发 和 调 试 ShockWave 程 序
本 章 中 介 绍 的 ShockWave 程 序 提 供 了 应 用 某 些 知 识 的 机 会 。 ShockWave 所 能 做的 仅 仅 是 显 示 任 意 色 彩 波 动 图 案 中 的 圆 环 。虽 然 它 编 译 没 有 错 误 ,但 ShockWave 不 能 正 常 运 行 , 程 序 存 在 有 两 个 错 误 , 一 个 比 较 明 显 , 另 一 个 不 太 明 显 。 使 用 调试 器 的 检 测 工 作 就 是 让 程 序 运 行 起 来 。
ShockWave 显 示 如 何 通 过 颜 色 层 次 来 获 得 三 维 效 果 。 你 将 需 要 配 备 一 个 24 位 色 的 视 频 适 配 器 , 并 至 少 需 1 M b 的 显 存 来 观 看 3-D 效 果 , 但 是 , 由 于 程 序 的目 的 是 演 示 调 试 器 的 作 用 。 在 图 11-11 中 , 显 示 了 成 功 调 试 后 的 程 序 状 态 。
图 11-11 ShockWave 样例程序
开 发 ShockWave
ShockWave 是 一 个 MFC 应 用 程 序 , 是 在 AppWizard 的 帮 助 下 创 建 的 。 你 可 以 在从 CD 中 的 Code\Chapter.11\ShockWav 的 子 文 件 夹 中 建 立 此 项 目 。 此 子 文 件 夹 中的 文 档 中 含 有 错 误 的 源 代 码 , 程 序 更 改 后 的 版 本 是 Shock_OK 文 件 夹 , 也 可 以 通过 接 下 来 的 6 个 步 骤 来 开 发 ShockWave 程 序 。 程 序 非 常 简 单 , 只 要 编 辑 它 的 视图 类 , 因 此 , 开 发 此 程 序 是 一 个 比 较 有 趣 的 练 习 , 它 需 要 键 入 的 代 码 并 不 多 。
步 骤 1 : 运 行 AppWizard 来 创 建 ShockWave 项 目
单 击 Visual C++ File ( 文 件 ) 菜 单 中 的 New ( 新 建 ) 命 令 , 选 择 Project( 项 目 ) 选项 卡 中 MFC AppWizard ( exe) 图 标 , 键 入 ShockWave 作 为 项 目 名 字 。 单 击 O K 以 运 行 AppWizard , 并 创 建 新 的 ShockWave 项 目 。 当 指 定 项 目 选 项 时 , 在
AppWizard 的 步 骤 1 中 选 择 Single Docume n (t
初 始 状 态 栏 和 步 骤 4 中 的 打 印 支 持 。
简 单 文 档 )按 钮 ,并 禁 用 的 工 具 栏 、
AppWizard 所 创 建 的 源 文 件 名 和 CD 上 的 略 有 不 同 , 它 严 格 控 制 字 符 的 长 度 为 8 个 字 母 或 更 少 , 以 适 应 以 前 的 文 本 编 辑 器 , 因 为 那 些 文 本 编 辑 器 不 能 识 别 长 文 件名 。 尽 管 文 件 名 不 同 , 文 件 中 的 源 代 码 却 与 这 6 个 步 骤 中 所 描 述 的 代 码 完 全 一样 。
步 骤 2 : 校 正 ShockWave 的 菜 单
ShockWave 要 求 的 仅 仅 是 一 个 菜 单 命 令 , 用 以 退 出 程 序 , 因 此 , 并 不 需 要AppWizard 添 加 到 菜 单 资 源 中 的 其 他 命 令 。 利 用 Visual C++ 菜 单 编 译 器 , 校 正ShockWave 菜 单 , 以 便 它 仅 有 一 个 File ( 文 件 ) 菜 单 和 一 个 Help ( 帮 助 ) 菜 单 。如 下 :
步 骤 3 : 利 用 ClassWizard 添 加 消 息 处 理 程 序 函 数
ShockWave 调 节 波 动 图 案 的 大 小 , 以 填 充 在 客 户 窗 口 里 , 并 适 应 窗 口 大 小 的 变化 。 也 可 以 在 客 户 区 域 利 用 鼠 标 单 击 将 新 的 波 纹 图 案 置 于 中 心 位 置 。为 了 响 应 这些 事 件 , ShockWave 利 用 名 字 为 OnSize 和 OnLButtonDown 的 处 理 函 数 , 来 跟 踪WM_SIZE 和 WM_LBUTTONDOWN 消 息 。
通 过 单 击 View 菜 单 中 的 ClassWizard 命 令 ,来 添 加 处 理 函 数 到 CShockWaveView 类 。 在 Message Map s( 消 息 映 射 ) 选 项 卡 中 , 设 置 CShockWaveView 作 为 类 名 , 并 在 M essage( 消 息 ) 框 中 选 择 WM_SIZE 。 单 击 Add Function ( 添 加 函 数 ) 按
钮 , 以 便 自 动 创 建 OnSize 函 数 , 它 用 来 处 理 WM_SIZE 消 息 。 对W M _ L B U T T O N D O W N 消 息 做 同 样 的 工 作 , 使 之 添 加 到 OnLButtonDown 函 数中 , 如 图 11-12 中 所 示 。 我 们 将 简 单 地 添 加 代 码 到 处 理 函 数 中 。 在 继 续 进 行 下 一步 操 作 之 前 , 关 闭 ClassWizard 对 话 框 。
图 11-12 在 ClassWizard 中创建 OnSize 和 OnLButtonDown 处 理 函 数
步 骤 4 : 编 辑 ShockWaveView.h 文 档
打 开 文 本 编 辑 器 中 的 ShockWaveView.h 文 件 ,并 定 位 到 CShockWaveView 类 声 明处 ( 在 配 套 CD 中 , 此 文 件 名 为 ShockVw.h ), 添 加 阴 影 代 码 如 下 :
// ShockWaveView.h : interface of the CShockWaveView class
//
/////////////////////////////////////////////////////////////////////////////
#define |
NUM_COLORS |
6 |
---|---|---|
#define #define |
RED 0 GREEN 1 |
|
#define |
BLUE 2 | |
#define #define |
CYAN 3 MAGENTA |
4 |
class CShockWaveView : public CView
{
protected: // create from serialization only CShockWaveView();
DECLARE_DYNCREATE(CShockWaveView)
.
.
.
步 骤 5 : 编 辑 ShockWaveView.ccp 文 件
ShockWaveView.ccp 文 件 ( 或 CD 中 的 ShockVw.ccp 文 件 ) 含 有 所 有 针 对CShockWaveView 类 的 实 现 的 详 细 信 息 。 它 也 要 求 一 些 附 加 信 息 , 在 带 有 阴 影 的行 中 有 所 显 示 。 W izardBar 提 供 了 在 文 本 编 辑 器 中 打 开 文 件 的 简 单 方 法 。 因 为 文本 编 辑 器 显 示 了 当 前 类 的 头 文 件 ,单击 W izardBar 中 的 棍 棒 图 标 ,打 开 CPP 文 件 。可 以 通 过 单 击 相 同 的 工 具 立 即 返 回 到 头 文 件 。
接 下 来 列 出 的 程 序 显 示 了 整 个 ShockWaveView.ccp 文 件 中 的 源 代 码 。在 每 条 重 要的 函 数 之 后 增 加 了 注 释 , 以 解 释 所 发 生 的 一 切 。
// ShockWave View.cpp : implementation of the CShockWaveView class
//
#include "stdafx.h" #include "ShockWave.h"
#include "ShockWaveDoc.h" #include "ShockWave View.h"
#includ e <math.h >
#ifdef _DEBUG
#define new DEBUG_NEW #undef THIS_FILE
static char THIS_FILE[] = __FILE__; #endif
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CShockWaveView
IMPLEMENT_DYNCREATE(CShockWaveView, CView)
BEGIN_MESSAGE_MAP(CShockWaveView, CView)
//{{AFX_MSG_MAP(CShockWaveView) ON_WM_SIZE() ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CShockWaveView construction/destruction
CShockWaveView::CShockWaveView()
{
}
CShockWaveView::~CShockWaveView()
{
}
CShockWaveView 构 造 器 用 6 种 色 彩 的 COLOR_REF 值 初 始 化 rgb 数 组 。当 显 示波 动 图 案 时 , CShockWave 随 机 地 选 择 其 中 的 一 个 色 彩 。 在 变 量 iColor 中 , 容 纳rgb 中 决 定 的 程 序 用 来 绘 制 波 动 图 案 的 颜 色 种 类 的 下 标 值 。
构 造 器 调 用 Windows API 函 数 GetSystemTime 来 检 索 当 前 系 统 时 间 的 ms 组 件 。这 个 值 的 范 围 是 0 到 999 , 并 为 srand 函 数 ( C 运 行 时 间 随 机 数 字 发 生 器 ) 提 供一 个 便 利 的 种 子 值 。
BOOL CShockWaveView::PreCreateWindow(CREATESTRUCT& cs)
{
}
ShockWave 显 示 的 鼠 标 光 标 是 十 字 形 ,而 不 是 一 般 的 箭 头 形 状 。利 用 这 种 轻 微 的变 化 , 用 户 可 以 更 明 确 客 户 区 域 中 的 目 标 是 什 么 , 然 后 单 击 它 。ShockWave 使 用
单 击 坐 标 作 为 下 一 个 波 动 图 案 的 中 心 。 为 了 改 变 窗 口 光 标 的 形 状 , CShockWaveView 使 PreCreateWindow 函 数 无 效 。MFC 框 架 在 它 创 建 程 序 的 主 窗口 之 前 , 调 用 PreCreateWindow 函 数 , 将 函 数 传 递 给 CREATESTRUCT 结构。此结 构 含 有 MFC 计 划 使 用 窗 口 的 设 置 。 使 PreCreateWindow 函 数 无 效 给 程 序 提 供了 一 个 机 会 , 在 MFC 创 建 窗 口 之 前 更 改 窗 口 特 征 , 诸 如 它 的 光 标 形 状 。
在 CShockWaveView 的 实 现 中 ,PreCreateWindow 首 先 加 载 标 准 的 W indows 十 字形 光 标 , 它 拥 有 一 个 IDC_CROSS 标 识 值 。 函 数 接 着 注 册 一 个 新 的 窗 口 类 , 给 它指 定 一 个 十 字 形 光 标 ,以 及 具 有 NULL 值 的 背 景 画 笔 。NULL 背 景 颜 色 标 志 着 在操 作 系 统 重 新 调 整 窗 口 大 小 时 不 能 重 新 绘 制 窗 口 背 景 。因 为 窗 口 颜 色 可 随 每 一 个新 的 波 纹 而 随 机 变 化 , ShockWave 本 身 负 有 绘 制 背 景 的 任 务 。
你 可 以 重 新 调 用 第 5 章 中 的 Color 样 例 程 序 来 绘 制 自 己 的 背 景 。 Color 程 序 不 能像 ShockWave 那 样 调 整 窗 口 创 建 标 志 , 但 是 , 可 以 捕 获 WM_ERASEBKGND 消息 , 以 防 止 操 作 系 统 绘 制 窗 口 。以 下 的 两 个 程 序 说 明 了 两 种 不 同 的 技 术 可 以 获 得相 同 的 结 果 :
/////////////////////////////////////////////////////////////////////////////
// CShockWaveView drawing
void CShockWaveView::OnDraw(CDC* pDC)
{
i = min( rectClient.right, rectClient.bottom ); // i = diameter
pDC->SetMapMode( MM_ISOTROPIC );
pDC->SetWindowExt( i, i );
pDC->SetViewportExt( rectClient.right, -rectClient.bottom );
pDC->SetViewportOrg( center.x, center.y );
i = max( rectClient.right, rectClient.bottom )/2; // i = radius
rect.SetRect( -i, -i, i, i );
iPeriod = i/(2*NUM_RINGS);
// Two loops: loop 1 displays one wave per iteration
// loop 2 draws one color gradation per iteration
for (j=0; j < NUM_RINGS; j++)
{
for (Angle=0.0, i=1; i < iPeriod; i++)
{
Angle += PI/iPeriod;
color = 128 + (DWORD)(128.0 * sin( Angle ));
if (color > 255)
color = 255;
switch (iColor)
{
case GREEN:
color <<= 8;
break;
case BLUE:
color <<= 16;
break;
case CYAN:
color = RGB( 0, (int) color, (int) color );
break;
case MAGENTA:
color = RGB( (int) color, 0, (int) color );
break;
case GRAY:
color = RGB( (int) color, (int) color,
(int) color );
break;
}
rect.InflateRect( -1, -1 );
pen.CreatePen( PS_SOLID, 1, color );
pDC->SelectObject( &pen );
pDC->Ellipse( rect );
pDC->SelectStockObject( BLACK_PEN );
pen.DeleteObject();
}
rect.InflateRect( -iPeriod, -iPeriod );
}
}
OnDraw 函 数 承 担 了 显 示 波 动 图 案 的 整 个 任 务 , 在 一 系 列 小 的 同 心 圆 中 画 出 每 道波 纹 。 每 个 圆 具 有 一 个 像 素 宽 , 从 波 纹 的 外 部 边 缘 开 始 , 然 后 向 中 央 作 用( 由 于
这 个 原 因 , 波 动 图 案 出 现 在 屏 幕 上 时 ,看 上 去 似 乎 是 向 内 扩 展 而 不 是 向 外 扩 展 )。对 每 一 个 像 素 圆 ,代 码 只 会 在 当 前 颜 色 的 强 度( 或 亮 度 )上 有 轻 微 的 增 加 或 减 弱 。强 度 的 递 减 使 波 纹 明 显 具 有 三 维 效 果 。
此 函 数 在 两 个 循 环 中 画 波 动 图 案 , 一 个 循 环 嵌 套 在 另 一 个 里 面 。 外 部 循 环 重 复 5 次 , 一 次 重 复 即 可 画 出 一 个 完 整 的 波 纹 。 内 部 循 环 每 重 复 一 次 , 便 画 出 一 个 像 素宽 度 的 圆 , 直 到 画 完 足 够 的 圆 , 形 成 整 个 波 纹 。 对 每 个 圆 , 内 循 环 创 建 一 个 新 的设 备 上 下 文 画 笔 , 采 用 iColor 确 定 的 当 前 色 彩 , 轻 微 调 整 颜 色 的 强 度 。 在 每 循 环一 次 后 ,强 度 从 中 间 亮 度 值 128 变 化 到 最 大 值 255 , 在 一 个 代 码 行 中 体 现 了 这 种变 化 :
color=128+(DWORD)(128.0*sin(Angle));
由 于 窗 口 背 景 的 颜 色 强 度 值 为 128 , 每 个 波 纹 似 乎 都 是 以 正 弦 曲 线 从 背 景 中 产生 。
/////////////////////////////////////////////////////////////////////////////
// CShockWaveView message handlers
void CShockWaveView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
}
void CShockWaveView::OnLButtonDown(UINT nFlags, CPoint point)
{
CView::OnLButtonDown(nFlags, point);
}
这 个 练 习 中 的 步 骤 3 使 用 ClassWizard 来 为 WM_SIZE 和 WM_LBUTTONDOWN 消 息 添 加 处 理 程 序 函 数 。 在 这 里 , 我 们 将 代 码 添 加 到 ClassWizard 创 建 的 存 根 函数 中 。
当 窗 口 大 小 发 生 变 化 时 , OnSize 处 理 程 序 将 波 纹 图 案 置 于 客 户 窗 口 的 中 心 , 并在 rectClient 中 记 录 新 窗 口 的 尺 寸 。OnDraw 函 数 利 用 这 些 尺 寸 ,以 保 证 波 动 图 案填 充 这 个 窗 口 。 OnLButtonDown 函 数 记 录 鼠 标 光 标 的 坐 标 , 它 将 确 定 下 一 波 形的 中 心 。 函 数 也 可 从 rgb 数 组 的 6 个 变 量 中 随 机 选 择 一 个 新 的 颜 色 , 并 调 用Invalidate , 以 便 窗 口 用 新 的 波 动 图 案 重 绘 。
步 骤 6 : 建 立 并 运 行 ShockWave.exe 程 序
确 定 Build 工 具 栏 显 示 W in32 Debug 作 为 当 前 的 程 序 配 置 :
单 击 工 具 栏 中 的 Build 按 钮 ( 或 者 选 择 Build ( 建 立 ) 菜 单 中 的 命 令 ), 以 创 建 一个 ShockWave 的 调 试 版 本 , 然 后 单 击 Execute ( 执 行 ) 命 令 , 以 便 运 行 程 序 。
调 试 ShockWave
第 一 次 运 行 ShockWave 时 , 第 一 个 错 误 是 显 而 易 见 的 ( 但 不 是 致 命 的 )。 客 户 窗口 似 乎 是 透 明 的 , Visual C++ 中 的 工 具 栏 和 文 本 出 现 在 ShockWave 的窗口里。你可 能 已 经 明 白 了 在 哪 里 出 的 错 , 但 是 , 让 我 们 用 调 试 器 来 调 试 程 序 , 以 弄 清 楚 问题 产 生 的 原 因 , 利 用 Exit ( 退 出 ) 命 令 关 闭 有 错 误 的 ShockWave 程 序 。
开 始 调 试 工 作 时 , 利 用 文 本 编 辑 器 来 寻 找 ShockWaveView.cc p( 或 ShockVw.ccp ) 文 档 。 错 误 最 有 可 能 在 视 图 类 中 产 生 , 因 为 它 含 有 要 求 大 量 变 换 的 部 分 源 代 码 。由 于 ShockWave 窗 口 中 存 在 一 些 错 误 , 所 以 , 应 该 怀 疑 开 始 的 PreCreateWindow 和 OnDraw 函 数 。 这 两 个 函 数 在 这 个 练 习 的 第 5 步 已 经 校 正 。第 1 个 函 数 设 置 窗口 特 性 , 第 2 个 函 数 设 置 窗 口 内 容 。
单 击 Edit ( 编 辑 ) 菜 单 中 的 Breakpoin t( 断 点 ) 命 令 , 在 Breakpoin t( 断 点 ) 对话 框 的 Location( 位 置 ) 选 项 卡 中 键 入 CShockWaveView:: PreCreateWindow , 按
Enter 键 , 并 在 同 一 地 方 键 入 CShockWaveView:: OnDraw , 这 可 以 将 位 置 断 点 设置 在 每 个 不 可 信 函 数 的 开 始 处 , 单 击 O K 按 钮 返 回 编 译 器 。
现 在 按 F5 启 动 调 试 器 , 硬 盘 的 活 动 表 示 Visual C++ 正 在 启 动 调 试 器 , 接 着 运 行ShockWave.exe 文 件 。 调 试 器 源 程 序 窗 口 接 着 显 示 指 令 箭 头 指 向CShockWaveView:: PreCreateWindow 函 数 的 第 一 行 。ShockWave 程 序 在 已 设 置 的两 个 位 置 断 点 处 的 第 一 个 位 置 停 下 来 。 Visual C++ 窗 口 现 在 看 上 去 有 点 像 图 11- 13 。
图 11-13 ShockWave 程 序 在 Visual C++ 调 试 器 中 的 断 点 处 停 止
PreCreateWindow 函 数 内 幕
单 击 图 11-13 所 示 的 Debug 工 具 栏 中 的 Variable s( 变 量 ) 工 具 , 以 显 现 Variables
( 变 量 ) 窗 口 。 Variables ( 变 量 ) 窗 口 列 出 了 最 后 执 行 行 中 引 用 的 变 量 , 在 此 情况 下 ,是 通 过 函 数 序 言 来 访 问 变 量 c s 。cs 变 量 指 向 MFC 用 来 创 建 ShockWave 窗
口 的 CREATESTRUCT 结 构 。 在 Variables ( 变 量 ) 窗 口 中 临 近 cs 名 字 的 地 方 , 单 击 小 的 加 号 ( + ) 按 钮 , 以 扩 大 显 示 结 构 成 员 变 量 。
如 果 一 些 变 量 名 太 长 , 不 能 填 充 在 栏 目 里 , 可 以 使 Variables ( 变 量 ) 窗 口 里 的
Name 栏 调 宽 一 些 。将 鼠 标 光 标 置 于 两 个 栏 目 标 签 之 间 的 分 隔 线 上 ,刚 好 在 Value
( 值 ) 标 签 的 左 边 , 并 左 右 拖 动 分 隔 线 , 重 新 调 整 栏 宽 。 双 击 分 隔 线 将 自 动 调 整栏 宽 , 以 便 在 栏 中 容 纳 最 长 的 变 量 名 。
按 F10 键 或 者 单 击 Step Over( 整 体 单 步 调 试 ) 工 具 按 钮 来 执 行 函 数 序 言 代 码 。指 针 在 PreCreateWindow 函 数 的 下 一 行 处 停 止 。
HCURSOR hCur=::LoadCursor(NULL,IDC_CROSS);
Variables ( 变 量 ) 窗 口 现 在 包 括 目 前 的 hCur 值 , 但 是 因 为 指 令 还 没 有 执 行 , 窗口 中 显 示 的 值 是 没 有 意 义 的 ,整 体 单 步 执 行 此 指 令 ,赋 予 hCur 返 回 给 LoadCursor API 函 数 的 值 。 新 值 以 红 色 出 现 , 来 指 示 最 后 一 条 指 令 已 经 改 变 hCur 的 值 。 颜色 代 码 是 一 些 调 试 器 窗 口 的 优 秀 特 征 , 可 以 让 你 迅 速 弄 清 楚 指 令 改 变 后 的 变 量值 。
按 F10 再 次 调 用 A fxRegisterWndClass:
cs.1pszClass= AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,hCur,NULL);
Variables( 变 量 ) 窗 口 显 示 , cs.lpszClass 现 在 指 向 一 个 有 效 类 名 。 有 时 有 点 类 似被 MFC 框 架 所 指 定 的 Afx:400000:3:13ce:0:0 。 cs.1pszClass 的 新 值 可 以 确 定AfxRegisterWndClass 已 被 正 确 运 行 。 这 不 值 得 惊 奇 , 因 为 操 作 系 统 正 确 创 建 了ShockWave 窗 口 ,类 必 须 正 确 注 册 ,窗 口 绘 制 ,而 不 是 窗 口 创 建 ,这 是 一 个 难 题 , 因 此 错 误 必 定 在 OnDraw 函 数 里 。 按 下 F5 键 , 或 者 从 Debug ( 调 试 ) 菜 单 中 选择 Go ( 转 到 ) 来 移 向 下 一 断 点 , 同 时 仔 细 观 察 屏 幕 。
OnDraw 函 数 内 幕
继 续 运 行 , 直 到 程 序 流 到 达 下 一 个 早 先 在 OnDraw 函 数 里 设 置 的 断 点 , 为 了 理 解程 序 的 这 一 点 , 可 以 看 见 ShockWave 窗 口 闪 烁 , 然 后 消 失 , ShockWave 窗 口 仍然 存 在 ,但 在 重 新 获 得 控 制 权 时 ,调 试 器 窗 口 将 会 覆 盖 它 。通 过 最 小 化 Visual C++ 可 以 展 现 ShockWave 窗 口 , 注 意 ShockWave 已 完 全 处 于 不 活 动 状 态 , 它 甚 至 没有 菜 单 栏 。 在 此 时 , 控 制 权 属 于 调 试 器 。
返 回 到 调 试 器 , 并 重 复 按 F10 键 , 以 便 单 步 通 过 数 据 声 明 , 向 下 到 此 段 代 码 :
pDC->SetMapMode( MM_ISOTROPIC );
pDC->SetWindowExt( i, i );
pDC->SetViewportExt( rectClient.right, -rectClient.bottom ); pDC->SetViewportOrg( center.x, center.y );
第 一 行 设 置 映 射 模 式 为 MM_ISOTROPIC , 确 保 波 形 以 圆 形 显 示 在 屏 幕 上 , 而 不是 椭 圆 。 接 下 来 的 两 行 设 置 窗 口 范 围 和 覆 盖 整 个 ShockWave 客 户 区 域 的 视 口 。最 后 一 行 在 窗 口 的 中 央 设 置 视 口 原 点 。 像 许 多 MFC 函 数 一 样 , 这 些 C D C 成 员函 数 在 成 功 时 返 回 一 个 正 值 , 或 返 回 NULL 以 指 出 某 一 问 题 。 这 些 函 数 失 败 是不 大 可 能 的 , 因 此 , 它 们 不 可 能 保 证 利 用 其 他 检 查 返 回 值 的 代 码 来 使 程 序 混 乱 。即 使 ShockWave 没 有 存 储 函 数 返 回 值 , 也 可 以 观 察 Variables ( 变 量 ) 窗 口 里 的这 些 值 , 以 便 确 保 函 数 正 确 运 行 。 每 次 整 体 调 试 一 个 函 数 时 , 窗 口 会 显 示 一 个 返回 值 , 如 下 图 所 示 :
如 果 一 个 函 数 返 回 错 误 代 码 ,当 你 调 试 它 时 , 调 试 器 能 将 此 返 回 值 转 换 成 所 需 要的 有 用 消 息 。 在 Watch ( 观 察 ) 窗 口 , 双 击 Nam e( 名 字 ) 栏 内 带 点 的 条 目 框 , 并 键 入 err,hr, 在 决 定 这 个 条 目 的 值 时 , 调 试 器 调 用 GetLastError API 函 数 , 并 将结 果 转 换 成 有 用 的 文 本 , 诸 如 “ The handle is invalid ( 此 句 柄 无 效 )”。
Register s( 寄 存 器 ) 窗 口 也 可 提 供 另 一 种 检 查 函 数 返 回 值 的 方 法 。 在 Intel 处 理 器中,一个 Win32 函 数 在 退 出 之 前 , 将 一 个 返 回 值 放 在 EAX 寄 存 器 中( 64 位 返 回值 占 据 了 EDX:EAX 寄 存 器 对 )。 为 了 检 查 函 数 的 返 回 值 , 在 单 步 进 行 整 体 函 数调 用 后 , 立 即 浏 览 一 下 Registers ( 寄 存 器 ) 窗 口 中 的 EAX ,像 Variables( 变 量 ) 窗 口 一 样 , Registers ( 寄 存 器 ) 窗 口 用 红 色 显 示 新 值 , 以 显 示 寄 存 器 在 最 后 指 令的 变 化 。由 于 以 上 函 数 返 回 来 的 值 都 不 是 0 ,因 此 ,可 以 得 知 本 段 代 码 运 行 正 确 。在 这 点 上 , ShockWave 在 绘 制 波 纹 图 案 的 两 个 循 环 的 开 始 做 好 准 备 。 但 是 , 有 时也 会 出 错 , 程 序 不 应 该 画 出 圆 的 波 纹 , 因 为 ShockWave 客 户 区 域 的 背 景 依 然 没
有 重 新 绘 制 。
重 新 调 用 第 5 步 中 校 正 过 的 PreCreateWindow 函 数 ,指 示 W indows 不 要 重 新 绘 制ShockWave 的 背 景 。 这 也 是 在 注 册 窗 口 类 时 , NULL 画 笔 值 赋 给AfxRegisterWndClass 的 目 的 。 因 此 , 按 照 要 求 , W indows 正 确 建 立 了 窗 口 , 而无 须 填 充 客 户 区 域 。 问 题 在 于 , ShockWave 不 这 样 做 , 必 须 有 人 来 重 新 绘 制 窗 口背 景 ,如 果 不 是 系 统 ,ShockWave 就 必 须 自 己 去 做 。解 决 第 一 个 错 误 的 办 法 就 是 : ShockWave 需 要 在 画 出 波 动 图 案 之 前 绘 制 窗 口 背 景 。
在 这 里 , 我 们 可 以 有 选 择 地 修 正 源 代 码 , 重 新 恢 复 执 行 Edit and Continue。 但 是Call Stack( 调 用 堆 栈 ) 窗 口 显 示 了 , OnDraw 函 数 是 几 个 嵌 套 函 数 行 中 的 最 后 一个 , 这 些 调 用 操 纵 内 核 和 MFC 框 架 。 这 意 味 着 所 有 的 更 改 不 会 马 上 起 作 用 , 仅当 函 数 下 次 执 行 时 才 有 效 。 因 此 , 可 以 终 止 调 试 器 并 返 回 编 译 器 。
这 将 引 出 一 个 比 较 有 趣 的 情 况 , 你 可 以 假 定 , 继 续 ShockWave 的 运 行 是 终 止 调试 的 一 种 方 法 , 而 且 这 种 方 法 是 经 过 三 思 的 。 可 以 利 用 Exi t( 退 出 ) 命 令 正 常 退出 ShockWave, 并 且 调 试 器 将 会 停 止 。 返 回 到 文 本 编 辑 器 , 好 , 试 一 试 它 , 按 下F5 继 续 运 行 ShockWave。
这 样 , 你 永 远 也 不 可 能 到 达 ShockWave 菜 单 , 因 为 每 次 按 下 F5 键 , 在 退 后 到CShockWaveView:: OnDraw 函 数 里 的 断 点 之 前 ,ShockWave 窗 口 只 是 简 短 地 出 现一 下 。 要 弄 清 楚 发 生 了 什 么 并 不 困 难 。 当 ShockWave 重 新 获 得 焦 点 时 , 它 将 出现 在 Visual C++ 的 顶 部 以 及 屏 幕 上 所 有 其 他 窗 口 之 上 。 Windows 将 W M _ PAINT
消 息 传 送 给 ShockWave, 告 诉 它 需 要 重 新 绘 制 。 但 在 重 新 绘 制 时 , 框 架 需 要 调 用
ShockWave 的 OnDraw 函 数 触 发 断 点 。 调 试 器 然 后 获 得 焦 点 , Visual C++ 就 在ShockWave 窗 口 上 显 示 自 己 。 每 次 你 按 下 F5 来 继 续 执 行 ShockWave 时 , 进 程 便重 复 无 限 的 循 环 。可 以 通 过 在 按 下 F5 键 之 前 删 除 或 禁 用 断 点 来 终 止 循 环 。但 是 , Debug ( 调 试 ) 工 具 栏 中 的 Stop Debug ( 停 止 调 试 ) 按 钮 ( 或 Debug ( 调 试 ) 菜单 中 与 其 等 同 的 命 令 ) 可 以 提 供 更 好 的 终 止 调 试 器 的 办 法 。
这 个 命 令 将 会 使 你 重 新 返 回 到 编 译 器 , 离 开 所 有 的 断 点 。校 正 和 重 建 ShockWave
绘 制 窗 口 背 景 并 不 要 求 太 多 的 代 码 , 在 文 本 编 辑 器 中 , 添 加 如 下 带 有 灰 色 的 程 序行 , 以 使 OnDraw 函 数 成 为 如 下 的 形 式 :
void CShockWaveView::OnDraw(CDC* pDC)
{
CPen pen;
CRect rect; COLORREF color; int i, j, iPeriod;
double Angle;
brush.DeleteObject() ;
.
.
.
灰 色 行 给 画 笔 分 配 当 前 被 iColor 指 出 的 颜 色 , 利 用 它 绘 制 客 户 区 域 , 接 着 , 删 除画 刷 , 在 此 处 使 用 新 的 代 码 , 再 建 立 ShockWave 的 调 试 版 本 , 并 使 用 Build ( 建立 ) 菜 单 的 Execute ( 执 行 ) 命 令 运 行 它 。 这 次 将 会 正 确 显 示 出 来 , 其 背 景 用 中间 色 度 的 绿 色 来 绘 制 。
第 二 个 错 误
ShockWave 还 存 在 第 二 个 错 误 , 第 二 个 错 误 比 第 一 个 错 误 更 有 趣 , 因 为 它 说 明 了在 调 试 器 没 有 运 行 时 , Visual C++ 怎 样 让 你 发 现 程 序 错 误 。 为 了 弄 清 楚 第 二 个 错误 , 在 ShockWave 窗 口 中 的 任 意 地 方 单 击 鼠 标 。 按 照 程 序 设 定 , 这 种 行 为 不 会清 除 窗 口 ,用 6 个 可 用 颜 色 中 的 一 个 来 重 绘 窗 口 ,在 鼠 标 单 击 的 坐 标 中 心 重 绘 波动 图 案 。 你 可 以 多 击 几 次 鼠 标 , 但 最 终 W indows 将 会 显 示 如 下 消 息 :
在 某 些 地 方 , ShockWave 试 图 访 问 不 属 于 它 的 内 存 , 当 这 些 在 程 序 的 发 行 版 本 中发 生 时 , 你 将 别 无 选 择 , 只 好 单 击 Close ( 关 闭 ) 按 钮 来 终 止 程 序 , 建 立 一 个 等同 的 调 试 版 本 , 再 次 启 动 调 试 器 , 并 重 复 错 误 的 产 生 情 况 。 但 以 上 消 息 提 供 了 另一 种 选 择 ,如 果 你 单 击 Debu g( 调 试 )按 钮 ,W indows 将 会 自 动 重 新 启 动 调 试 器 , 甚 至 即 使 在 Visual C++ 没 有 正 确 运 行 的 情 况 下 。 你 将 会 发 现 , 自 己 在 查 看ShockWave 程 序 ,因 为 它 在 错 误 之 后 立 即 出 现 。没 有 必 要 猜 测 哪 一 行 引 起 了 保 护错 误 , 因 为 黄 色 的 指 令 指 针 箭 头 正 指 向 它 。 M icrosoft 将 此 特 征 称 作 即 时 调 试 。
根 据 源 程 序 窗 口 显 示 , 程 序 在 CShockWaveView:: OnDraw 函 数 里 的 第 一 条 指 令处 运 行 失 败 :
brush.CreateSolidBrush(rgb[iColor]);
这 是 我 们 刚 刚 添 加 的 一 行 , 它 建 立 了 一 个 画 刷 , 以 便 OnDraw 绘 制 ShockWave 客 户 区 域 的 背 景 。 变 量 iColor 中 是 rgb 数 组 的 下 标 , 如 下 所 示 , 它 在ShockWaveView.h 中 已 经 声 明 了 。
#define NUM_COLORS 6
.
.
.
COLORREF rgb[NUM_COLORS];
iColor 的 当 前 值 决 定 了 用 于 背 景 画 刷 使 用 的 颜 色 。 在 Variables( 变 量 ) 窗 口 中 查看 一 下 iColor, 它 的 值 应 该 为 6 或 更 多 , 这 意 味 着 画 刷 目 前 的 颜 色 是 rgb 数 组 中的 第 iColor 个 元 素 。
这 里 存 在 一 个 问 题 , 给 iColor 赋 予 一 个 比 5 大 的 值 , 这 意 味 着 程 序 试 图 访 问 rgb 数 组 中 的 一 个 元 素 , 而 这 个 元 素 并 不 存 在 。 很 明 显 , 这 是 保 护 错 误 。 我 们 已 经 找到 了 错 误 , 但 是 不 知 道 是 什 么 原 因 引 起 的 。 iColor 变 量 所 接 收 的 值 仅 仅 是CShockWaveView:: OnLButtonDown 函 数 里 的 , 当 系 统 在 客 户 区 域 检 测 鼠 标 时 , 它 便 开 始 运 行 。
void CShockWaveView::OnLButtonDown(UINT nFlags, CPoint point)
{
CView::OnLButtonDown(nFlags, point); center = point;
iColor = rand();
Invalidate( FALSE );
}
此 行 :
iColor=rand ( );
指 定 iColor 为 一 个 随 机 数 , 是 从 rand 函 数 中 得 到 的 。 这 个 C 运 行 函 数 返 回 的 值范 围 是 从 0 到 RAND_MAX , Stdlib.h 头 文 件 定 义 为 0x7FFF 或 32767 。 iColor 会以 这 样 高 的 值 结 束 就 不 奇 怪 了 。我 们 需 要 保 证 iColor 的 值 永 远 不 会 超 过 rgb 数 组中 的 元 素 数 目 , 以 便 OnDraw 函 数 只 访 问 有 效 的 颜 色 元 素 。 可 以 利 用 下 行 语 句 , 通 过 重 置 错 误 行 来 限 制 iColor 的 值 。
iColor=rand() % NUM_COLORS;
这 样 就 解 决 了 第 二 个 错 误 ,方 法 是 限 制 iColor 的 值 为 从 0 到 5 的 范 围 。如 果 你 重建 了 ShockWave , 并 再 次 通 过 Execute 命 令 执 行 了 它 , 将 会 看 到 程 序 的 运 行 将 如愿 以 偿 。
特 殊 的 调 试 情 况
W in32 程 序 可 以 完 成 许 多 任 务 , 并 且 , 在 本 章 中 所 述 的 这 些 简 单 例 子 中 , 几 乎 不可 能 直 接 应 用 于 你 自 己 的 程 序 中 。 这 就 是 为 什 么 我 将 尽 量 把 重 心 集 中 在 技 术 上 , 而 不 是 细 节 上 的 原 因 , 无 论 你 的 W in32 应 用 程 序 多 么 复 杂 , 或 者 多 么 不 正 常 , 也 要 有 自 信 心 , Visual C++ 调 试 器 能 给 你 提 供 帮 助 。
这 里 说 明 如 何 利 用 先 进 的 W in32 特 征 来 调 试 程 序 。Visual C++ 调 试 器 能 截 取 异 常情 况 。 用 多 线 程 处 理 应 用 程 序 , 调 试 ActiveX 客 户 和 服 务 器 应 用 程 序 , 所 有 这 些都 能 快 速 完 成 。 当 被 调 试 的 控 制 程 序 在 一 台 计 算 机 上 运 行 时 ,调 试 器 还 能 在 另 一台 计 算 机 上 运 行 。
调 试 异 常
利 用 C++ 异 常 处 理 机 制 , 当 意 想 不 到 的 错 误 发 生 时 , 程 序 可 以 保 留 控 制 权 。 当 函
数 检 测 到 一 个 错 误 时 , 它 通 过 调 用 throw 关 键 字 来 通 知 异 常 处 理 程 序 , 异 常 处 理程 序 利 用 catch 来 获 得 通 知 。 如 果 没 有 捕 获 异 常 情 况 的 处 理 程 序 , 调 试 器 会 通 知你 没 有 捕 获 异 常 。C 程 序 也 能 执 行 结 构 化 异 常 处 理 , 它 用 _try 和 _except 语句,而不 是 throw 和 catch 语 句 。
在 图 11-14 中 显 示 了 Exceptions( 异 常 ) 对 话 框 , 可 以 从 中 指 定 调 试 器 应 该 怎 样处 理 每 种 类 型 的 异 常 情 况 。 通 过 单 击 Debug 菜 单 上 的 Exception s( 异 常 ) 命 令 来调 出 此 对 话 框 。 你 可 以 为 程 序 中 可 能 发 生 的 每 种 异 常 情 况 设 置 两 个 选 项 中 的 一项 : Stop Alway s( 永 远 停 止 ) 或 Stop If Not Handled( 如 果 不 处 理 便 停 止 )。
如 果 为 一 种 异 常 指 定 Stop If Not Handled , 当 异 常 出 现 时 , 调 试 器 将 一 条 消 息 写到 Outpu t( 输 出 ) 窗 口 中 , 但 不 会 终 止 程 序 , 或 用 一 个 对 话 框 来 通 知 你 , 除 非 异常 处 理 程 序 解 决 异 常 时 失 败 。此 时 修 改 问 题 或 检 查 源 代 码 以 弄 清 楚 异 常 发 生 的 位置 就 太 晚 了 , 因 为 程 序 已 经 出 现 异 常 , 并 正 在 运 行 异 常 处 理 程 序 。
图 11-14 Exceptions( 异 常 ) 对 话 框
如 果 为 一 种 异 常 情 况 指 定 Stop Alway s, 你 会 对 异 常 情 况 的 处 理 具 有 更 多 的 控 制权 。 当 异 常 情 况 产 生 时 , 调 试 器 将 会 立 即 终 止 程 序 , 更 新 源 程 序 窗 口 , 以 显 示 错误 指 令 , 并 在 异 常 情 况 处 理 函 数 获 得 控 制 权 之 前 通 知 你 。 有 些 情 况 下 , 可 以 在Variables( 变 量 ) 窗 口 中 更 改 一 些 错 误 的 变 量 来 处 理 异 常 情 况 。 如 果 你 接 着 按 下F5 来 继 续 运 行 程 序 , 对 话 框 将 会 询 问 你 , 是 否 想 忽 略 异 常 , 返 回 到 程 序 异 常 处理 函 数 。 如 果 你 修 正 了 问 题 , 单 击 N o 按 钮 。 否 则 , 单 击 Yes 按 钮 , 以 便 将 控 制
权 传 递 给 异 常 处 理 程 序 。 如 果 异 常 处 理 程 序 不 能 修 正 问 题 , 调 试 器 会 终 止 程 序 , 并 再 次 通 知 , 就 像 你 已 经 选 择 了 Stop If Not Handled 一 样 。 因 为 Stop Always 选项 使 用 处 理 器 的 调 试 寄 存 器 , 对 没 有 调 试 寄 存 器 的 处 理 器 , 这 个 选 项 不 合 适 。
显 示 在 图 11-14 中 的 Exceptions 列 表 框 含 有 一 个 系 统 异 常 的 默 认 列 表 。 可 以 添 加或 从 列 表 中 删 除 异 常 , Visual C++ 在 项 目 的 OPT 文 件 中 能 保 存 新 的 列 表 。对 于 不在 列 表 中 的 异 常 , 调 试 器 的 处 理 像 对 待 Stop If Not Handled 异 常 那 样 。 每 个 异 常有 一 个 唯 一 的 数 字 , 系 统 异 常 利 用 EXCEPTION 前 缀 在 W inbase.h 头 文 件 中 定义 , 诸 如 EXCEPTION_ACESS_VIOLATION 。
为 了 添 加 新 的 异 常 到 异 常 列 表 中 , 调 用 Exception s( 异 常 ) 对 话 框 , 并 在 Number 控 件 中 键 入 异 常 号 ,在 Name 控 件 中 键 入 异 常 情 况 名 。单 击 Stop Always 或 Stop If Not Handled 按 钮 , 接 着 单 击 Add 按 钮 。 为 了 删 除 异 常 情 况 , 从 Exceptions 列 表中 选 择 它 , 并 单 击 Remove ( 删 除 ) 按 钮 。 如 果 你 改 变 主 意 , 并 想 恢 复 所 有 删 除的 系 统 异 常 , 单 击 Rese t( 重 置 ), 如 果 为 异 常 情 况 改 变 选 项 , 诸 如 它 的 名 字 , 单击 Change( 改 变 ) 按 钮 来 使 更 改 成 为 永 久 的 。
调 试 线 程
在 一 个 运 行 的 应 用 程 序 中 , 一 个 线 程 就 是 一 条 运 行 路 径 。 每 个 应 用 程 序 至 少 运 行一 个 线 程 , 即 主 线 程 或 根 线 程 , 它 也 可 能 生 成 其 他 线 程 。 当 调 试 具 有 多 个 线 程 的程 序 时 , 你 可 以 选 择 要 调 试 哪 个 线 程 , 并 按 照 其 执 行 流 进 行 调 试 。
只 有 在 调 试 器 开 始 执 行 后 , 才 可 以 选 择 一 个 要 调 试 的 线 程 。 首 先 , 在 需 要 的 地 方设 置 一 个 断 点 , 当 执 行 程 序 在 断 点 处 停 下 来 时 , 通 过 断 点 的 所 有 线 程 均 被 挂 起 。单 击 Debug 菜 单 的 Threads( 线 程 ) 来 调 出 Threads 对 话 框 , 从 线 程 列 表 中 选 出所 需 要 的 线 程 , 并 单 击 Set Focu s( 设 置 焦 点 ) 按 钮 。 当 你 继 续 单 步 调 试 程 序 时 , 调 试 器 沿 着 具 有 焦 点 的 线 程 进 行 调 试 。 为 了 防 止 别 的 线 程 运 行 同 样 的 代 码 , 在Threads ( 线 程 ) 对 话 框 中 , 将 其 他 线 程 挂 起 。 后 面 , 你 可 以 恢 复 被 挂 起 的 程 序 , 方 法 是 在 同 样 的 对 话 框 中 选 择 它 , 并 单 击 Resum e( 恢 复 ) 按 钮 。
调 试 动 态 链 接 库
在 Visual C++ 中 调 试 动 态 链 接 库 与 调 试 普 通 的 应 用 程 序 没 有 什 么 区 别 ,不 同 的 只是 调 试 器 启 动 库 的 调 用 程 序 , 并 没 有 加 载 DLL 文 件 本 身 。当 调 用 程 序 需 要 库 时 , 操 作 系 统 才 会 照 管 加 载 的 库 。当 控 制 到 达 库 代 码 中 的 断 点 时 ,所 运 行 的 调 用 程 序和 DLL 均 被 挂 起 。 在 调 试 动 态 链 接 库 中 , 唯 一 额 外 的 步 骤 就 是 标 识 所 调 用 的 应
用 程 序 ,以 便 调 试 器 能 运 行 它 。显 示 Project Settin g(s 项 目 设 置 )对 话 框 中 的 Debug
( 调 试 ) 选 项 卡 , 方 法 是 选 择 Project( 项 目 ) 菜 单 中 的 Settings( 设 置 ) 命 令 , 然 后 键 入 或 浏 览 所 调 用 应 用 程 序 的 文 件 名 和 路 径 。
如 果 离 开 Executable For Debug Session ( 调 试 会 话 的 可 执 行 程 序 ) 对 话 框 , 当 你开 始 调 试 动 态 链 接 库 时 , 调 试 器 会 提 示 文 件 名 。 在 线 帮 助 建 议 你 在 Categor y( 分类 ) 框 中 选 择 Additional DLL( 附 加 的 动 态 链 接 库 ),在 Local Nam e( 局 部 名 称 )
栏 中 双 击 蓝 色 的 条 目 框 , 并 浏 览 想 调 试 的 动 态 链 接 库 文 件 。 而 且 , 依 靠 所 设 置 的路 径 , 当 你 进 行 调 试 时 , 操 作 系 统 在 定 位 DLL 文 件 时 仍 然 会 出 现 错 误 。 避 免 此类 问 题 的 方 法 是 , 忽 略 Additional DLLs 设 置 , 并 在 项 目 Debug 文 件 夹 中 放 置 一个 调 用 程 序 的 可 执 行 文 件 副 本 。将 调 用 程 序 和 动 态 链 接 库 均 放 置 于 相 同 的 文 件 夹中 , 以 确 保 Windows 总 是 能 够 调 出 动 态 链 接 库 。
在 库 源 代 码 中 设 置 断 点 后 , 选 择 G o( 转 到 ) 命 令 , 或 者 按 下 F5 键 , 来 启 动 调 试器 。 如 果 被 调 用 的 程 序 是 调 试 或 发 行 的 格 式 , 这 两 种 启 动 方 式 将 没 有 多 大 区 别 。但 是 , 在 后 一 种 情 况 下 , Visual C++ 显 示 一 条 消 息 , 告 知 你 程 序 没 有 符 号 消 息 。因 为 DLL 文 件 正 在 被 调 试 , 而 不 是 被 调 用 的 程 序 。 这 种 消 息 仅 仅 是 一 个 手 续 , 提 醒 你 不 能 跟 随 后 面 的 执 行 流 进 入 被 调 用 的 程 序 。 单 击 OK 按 钮 开 始 调 试 工 作 。
调 试 OLE/ActiveX 应 用 程 序
除 了 诸 如 ActiveX 控 件 一 类 的 进 程 内 的 服 务 器 外 , COM 的 工 作 就 像 是 远 程 过 程调 用 ( RPC ) 一 样 , 从 一 个 应 用 程 序 到 另 一 个 应 用 程 序 。 一 般 来 说 , 正 在 调 用 的应 用 程 序 是 客 户 ,被 调 用 的 程 序 是 服 务 器 。如 果 你 只 想 开 发 一 个 服 务 器 或 者 一 个客 户 , 应 该 关 注 所 支 持 的 远 程 过 程 调 用 一 端 所 发 生 的 情 况 。 在 这 种 情 况 下 , 对 于调 试 OLE/ActiveX 应 用 程 序 没 有 特 殊 的 地 方 。对 客 户 而 言 ,在 访 问 服 务 器 的 调 用中 设 置 一 个 断 点 , 运 行 调 试 器 , 当 断 点 被 激 活 时 , 确 保 参 数 能 正 确 地 初 始 化 , 然后 整 体 单 步 调 试 此 调 用 , 检 查 所 有 的 返 回 值 。 当 调 试 一 个 服 务 器 时 , 在 处 理 程 序
函 数 中 设 置 一 个 断 点 , 以 便 接 收 远 程 过 程 调 用 , 并 运 行 调 试 器 来 启 动 服 务 器 。 然后 , 转 换 到 调 试 器 , 程 序 应 当 在 断 点 处 被 中 断 。
如 果 开 发 的 是 同 时 工 作 的 客 户 和 服 务 器 , 利 用 Visual C++ 调 试 器 , 可 以 对 远 程 过程 调 用 的 两 端 能 进 行 调 试 , 即 使 你 所 开 发 的 应 用 程 序 是 一 个 独 立 项 目 ,调 试 器 所
要 求 的 仅 仅 是 一 个 额 外 的 步 骤 。在 两 个 项 目 中 ,从 Tool(s 工 具 )菜 单 中 选 择 Options
( 选 项 ),然 后 单 击 Debu g( 调 试 )选 项 卡 ,并 启 用 OLE RPC Debuggin g( OLE RPC 调 试 ) 复 选 框 。 所 有 这 些 均 能 实 现 , 只 是 在 W indows NT 中 , 必 须 要 有 管 理 员 的特 权 才 能 启 用 此 复 选 框 。
如 本 书 的 第 4 部 分 所 述 , ActiveX 控 件 充 当 动 态 链 接 库 的 服 务 器 , 在 同 一 地 址 空间 内 利 用 控 件 来 执 行 。调 试 ActiveX 控 件 与 调 试 普 通 的 动 态 链 接 库 没 有 很 大 的 区别 ,一 个 OLE/ActiveX 服 务 器 作 为 一 个 应 用 程 序 运 行 ,而 不 是 作 为 一 个 动 态 链 接库 来 运 行 , 而 且 , 在 不 同 的 地 址 上 运 行 , 而 不 是 在 客 户 上 , 通 过 PRC 穿 过 进 程边 界 相 互 通 信 。 Visual C++ 处 理 这 种 情 况 的 方 法 是 , 运 行 调 试 器 的 两 个 实 例 , 一个 对 客 户 ,一 个 对 服 务 器 。当 调 试 一 个 远 程 过 程 调 用 的 两 端 时 ,必 须 有 两 种 要 求 , 它 们 都 没 有 任 何 限 制 。 首 先 , 如 上 所 述 , 必 须 利 用 OLE PRC Debugging 复 选 框 , 其 次 , 服 务 器 应 用 程 序 必 须 是 局 部 的 。 也 就 是 说 , 它 必 须 在 同 一 机 器 上 作 为 客 户运 行 。 Visual C++ 调 试 器 不 能 启 动 在 不 同 机 器 上 运 行 的 远 程 服 务 器 , 通 过 网 络 为调 试 客 户 应 用 程 序 ,需 沿 着 该 点 调 用 服 务 器 的 运 行 路 径 进 行 。如 果 接 着 进 入 调 用的 内 部 单 步 调 试 中 , Visual C++ 会 启 动 另 一 个 调 试 器 实 例 , 如 果 它 可 用 , 就 会 加载 服 务 器 源 代 码 。 接 着 , 在 服 务 器 响 应 远 程 调 用 时 , 单 步 调 试 通 过 服 务 器 。 当 服
务 器 从 RPC 返 回 时 , 控 制 权 恢 复 调 试 器 的 第 一 个 实 例 , 并 且 , 在 调 用 后 , 又 返回 到 了 客 户 的 下 一 指 令 。 调 试 器 的 第 2 个 实 例 直 到 将 服 务 器 终 止 下 来 它 才 会 终止 。 所 以 , 现 在 立 即 穿 越 RPC 桥 接 , 再 次 从 客 户 进 入 服 务 器 进 行 单 步 调 试 , 不要 等 待 启 动 新 的 调 试 器 实 例 。
你 也 可 以 从 服 务 器 端 开 始 调 试 ,尽 管 你 必 须 手 工 启 动 客 户 应 用 程 序 ,在 想 终 止 服务 器 的 地 方 设 置 一 个 断 点 , 接 着 按 下 F5 来 启 动 调 试 器 , 并 启 动 服 务 器 。 转 换 到客 户 应 用 程 序 ,并 调 出 所 要 求 的 程 序 。接 着 转 换 返 回 调 试 器 ,以 继 续 调 试 服 务 器 。如 果 你 从 RPC 处 理 程 序 函 数 中 退 出 单 步 调 试 , 并 进 入 客 户 应 用 程 序 。Visual C++ 启 动 调 试 器 的 另 外 一 个 实 例 ,并 将 它 与 执 行 客 户 关 联 。新 的 调 试 器 实 例 只 有 在 退出 客 户 应 用 程 序 时 才 会 终 止 ,你 可 以 在 远 程 过 程 调 用 的 不 同 位 置 继 续 调 试 客 户 和服 务 器 , 而 不 用 管 在 哪 个 应 用 程 序 里 开 始 调 试 。
用 两 台 计 算 机 进 行 调 试
在 调 试 时 , 经 常 由 于 调 试 器 竞 争 被 调 试 程 序 的 屏 幕 空 间 而 引 起 问 题 ,当 被 调 试 程序 运 行 时 , 它 在 屏 幕 上 显 示 了 正 常 的 输 出 值 , 但 是 , 调 试 器 必 须 使 用 屏 幕 与 用 户联 系 。 基 于 DOS 的 调 试 器 像 CodeView 对 此 类 问 题 有 一 个 有 效 的 解 决 方 法 , 因为 调 试 器 仅 仅 在 文 本 模 式 中 运 行 , 程 序 员 能 够 将 系 统 连 接 一 个 独 立 的 单 色 监 视器 , 用 来 显 示 调 试 器 的 源 程 序 窗 口 、 寄 存 器 , 并 观 察 变 量 。 同 时 , 被 调 试 程 序 在EGA 或 V G A 主 系 统 监 视 器 上 正 常 显 示 。 两 个 监 视 器 会 使 桌 面 显 得 拥 挤 , 但 是 ,
调 试 起 来 会 更 加 简 单 、 有 效 。
这 种 解 决 方 法 在 W indows 下 是 不 可 能 的 , 因 为 调 试 器 不 再 在 文 本 模 式 下 运 行 , 调 试 器 和 程 序 使 用 相 同 的 显 存 来 运 行 , 像 其 他 的 W indows 程 序 一 样 , 两 者 必 须在 一 个 或 更 多 个 窗 口 中 显 示 其 输 出 值 。 这 意 味 着 , 当 运 行 的 程 序 被 中 断 , 并 且 当调 试 器 获 得 控 制 权 时 , 调 试 器 窗 口 很 容 易 就 会 覆 盖 属 于 被 调 试 程 序 的 窗 口 。当 我们 在 前 一 章 中 调 试 ShockWave 程 序 时 , 就 看 到 了 这 种 现 象 。
像 以 往 的 CodeView 一 样 , Visual C++ 调 试 器 提 供 了 一 种 解 决 办 法 , 将 冲 突 的 显示 分 离 开 来 , 引 导 它 们 到 各 自 的 监 视 器 中 。 但 是 , 你 不 是 使 用 一 个 单 色 监 视 器 , 而 是 需 要 一 个 额 外 的 能 够 运 行 程 序 的 计 算 机 及 其 主 机 环 境 , 无 论 是 W indows 95 还 是 W indows NT ( Power Macintosh 不 再 支 持 〕, 因 为 Visual C++ 不 再 支 持 串 行空 调 制 解 调 器 链 接 , 所 以 , 两 个 计 算 机 必 须 通 过 网 络 链 接 起 来 。 一 个 计 算 机 作 为主 机 , 以 显 示 调 试 器 窗 口 , 而 另 一 台 计 算 机 , 指 定 为 目 标 计 算 机 或 远 程 计 算 机 , 用 来 显 示 被 调 试 程 序 的 输 出 值 。 Visual C++ 称 这 种 安 排 为 远 程 调 试 。
远 程 调 试 由 三 步 组 成 :
-
拷 贝 文 件 到 远 程 计 算 机 上 。
-
配 置 主 机 。
-
配 置 远 程 计 算 机 。
步 骤 1 : 拷 贝 文 件 到 远 程 计 算 机 上
拷 贝 文 件 Msvcrt.dll , Tin0t.dll, Dm.dll, Msvcp60.dll, Msdis110.dll 到 远 程 计 算 机 的W indows 文 件 夹 中 ,如 果 被 调 试 程 序 在 W indows NT 下 运 行 ,还 需 要 拷 贝 PsAPI.dll 文 件 。这 些 文 件 操 作 调 试 器 的 远 程 监 视 器 程 序 ,文 件 在 Common\MSDev98\Bin 和VC98\Redist 的 Visual C++ 文 件 夹 中 的 子 文 件 夹 中 。
步 骤 2 : 配 置 主 机
配 置 主 机 能 告 知 Visual C++ 在 哪 儿 找 到 你 想 调 试 的 程 序 ,以 及 它 所 运 行 的 远 程 计
算 机 种 类 ,和 两 台 计 算 机 的 连 接 类 型 。首 先 ,单 击 Proje c(t 项 目 )菜 单 中 的 Settings
( 设 置 ),在 Project Settings( 项 目 设 置 ) 对 话 框 的 Debug 选 项 卡 中 标 有 Remote Executable Path And File Nam e( 远 程 可 执 行 路 径 和 文 件 名 ) 的 文 本 框 中 指 定 程 序路 径 。这 个 路 径 可 以 视 作 从 自 调 试 器 正 在 运 行 的 主 机 上 进 行 查 看 。 在 这 个 文 本 框中 ,输 入 程 序 路 径 ,作 为 M svcmon.exe 从 远 程 计 算 机 上 的 所 在 位 置 进 行 查 看 的 程序 路 径 。
接着,从 Build( 建 立 ) 菜 单 中 选 择 Debugger Remote Connectio n( 调 试 器 远 程 连接 ), 以 显 示 Remote Connection ( 远 程 连 接 ) 对 话 框 。 选 择 TCP\IP 作 为 远 程 计算 机 的 连 接 类 型 ,然 后 单 击 Remote Connectio n( 远 程 连 接 )对 话 框 的 Setting s( 设置 ) 按 钮 。 这 将 会 显 示 另 一 个 对 话 框 , 可 以 查 询 通 信 设 置 , 包 括 远 程 计 算 机 的 密码 。
步 骤 3 : 配 置 远 程 计 算 机
在 远 程 计 算 机 上 运 行 M svcmon.exe 来 调 试 监 视 器 程 序 , 当 Visual C++ Debug Monitor( 调 试 监 视 器 ) 对 话 框 出 现 时 , 单 击 Settings( 设 置 ) 按 钮 , 并 在 接 下 来 的步 骤 中 键 入 相 同 的 密 码 , 单 击 O K 按 钮 就 退 出 了 对 话 框 , 然 后 , 在 主 机 上 开 始 正常 运 行 调 试 器 。
第 12 章 编 译 器 优 化
M icrosoft Visual C++ 编 译 器 将 C 和 C++ 源 代 码 转 换 成 机 器 码 ,对 程 序 的 调 试 版 本来 说 , 这 种 转 换 是 真 正 进 行 的 , 在 完 成 的 可 执 行 程 序 中 , 产 生 了 一 系 列 低 级 的 机器 指 令 ,它 们 完 全 表 示 源 程 序 的 高 级 指 令 。发 行 版 本 给 编 译 器 提 供 了 更 大 的 工 作范 围 , 因 为 完 全 进 行 源 程 序 转 换 是 没 有 必 要 的 , 甚 至 并 不 是 人 们 所 希 望 的 。 当 创建 发 行 版 本 时 , 编 译 器 有 不 同 的 任 务 , 它 产 生 一 个 最 小 的 或 最 快 的 目 标 代 码 , 而不 给 程 序 引 进 新 的 和 不 能 预 料 的 行 为 。
这 章 有 两 个 目 的 ,一 个 是 使 你 熟 悉 Visual C++ 优 化 代 码 和 处 理 影 响 优 化 的 不 同 情况 的 方 法 。 了 解 一 些 内 在 的 进 程 信 息 , 有 助 于 你 ( 而 不 是 优 化 器 ) 更 好 地 工 作 , 从 而 使 来 源 代 码 可 以 通 过 优 化 来 进 行 改 进 。第 2 个 目 的 就 是 解 释 Visual C++ 中 的许 多 开 关 和 选 项 , 它 们 是 用 来 约 束 优 化 进 程 的 , 以 便 你 能 够 更 精 确 地 理 解 , 在 打开 或 关 闭 一 个 开 关 时 , 编 译 器 的 行 为 将 会 如 何 作 用 。
为 了 完 成 这 两 个 目 标 , 本 章 粗 略 地 分 成 两 部 分 , 前 半 部 分 描 述 编 译 器 优 化 的 全貌 , 解 释 技 术 , 并 讨 论 它 们 的 优 缺 点 。 第 后 半 部 分 将 前 半 部 分 的 概 述 同 Project Settings 对 话 框 中 的 一 个 特 定 的 编 译 器 开 关 联 系 起 来 。 最 后 , 将 详 细 说 明 优 化 问题 , 在 汇 编 级 上 检 查 一 个 优 化 代 码 的 例 子 。
优 化 入 门
在 本 章 的 讨 论 中 , 仔 细 区 分 了 运 行 的 速 度 和 尺 寸 , 有 些 读 者 可 能 奇 怪 , 为 什 么 这里 会 区 分 它 们 , 不 是 越 小 的 代 码 速 度 越 快 吗 ? 我 们 感 觉 是 这 样 , 广 告 也 是 这 样 讲的 , 它 保 证 产 品 是 “ 轻 巧 而 快 速 ” 或 “ 小 巧 而 灵 活 ”。 但 实 际 上 , 在 可 执 行 代 码的 速 度 和 大 小 之 间 , 没 有 什 么 严 格 的 界 限 , 而 且 , 提 高 质 量 的 优 化 可 能 反 过 来 影响 其 他 方 面 。
这 里 存 在 三 种 代 码 优 化 级 别 ,程 序 员 和 编 译 器 都 可 以 使 用 它 们 , 最 高 级 是 算 法 级别 , 这 属 于 程 序 员 。 例 如 , 快 速 排 序 算 法 能 很 容 易 执 行 一 种 简 单 的 插 入 排 序 , 并使 用 二 叉 树 方 法 搜 寻 一 个 查 询 表 , 这 比 简 单 地 从 头 到 尾 扫 描 要 快 得 多 。 不 幸 的是 , 快 速 算 法 要 比 简 单 算 法 使 用 更 多 的 代 码 , 并 且 , 简 单 算 法 的 方 法 要 更 直 接 了当 。
最 低 级 的 优 化 , 被 称 为 窥 视 孔 优 化 , 它 属 于 编 译 器 。 在 这 种 级 别 上 , 编 译 器 利 用机 器 特 定 的 方 法 来 保 存 一 个 消 息 或 时 钟 周 期 , 当 通 过 整 个 程 序 积 累 时 ,存 储 将 会更 有 意 义 。窥 视 孔 优 化 通 常 由 于 代 码 比 较 小 或 者 速 度 较 快 而 引 起 , 但 并 非 总 是 这样 , 例 如 , Intel 指 令 :
and dword ptr [iVar], 0
它 有 3 个 字 节 , 比 较 小 , 但 是 , 它 要 比 下 面 的 指 令 慢 3 倍 。
mov dword ptr [iVar], 0
两 个 指 令 均 将 0 赋 给 整 数 iVar, 在 80486 和 奔 腾 处 理 器 上 , 如 下 指 令 :
push 1 pop eax
它 采 用 3 个 字 节 和 2 个 时 钟 周 期 , 尺 寸 将 近 一 半 , 但 是 , 速 度 只 有 如 下 等 效 指 令速 度 的 一 半 :
mov eax, 1
中 间 级 别 的 优 化 介 于 算 法 和 窥 视 孔 优 化 级 别 之 间 ,它 包 括 了 传 统 的 优 化 技 术 ,像子 表 达 式 的 消 除 、 复 制 传 播 和 循 环 启 动 , 所 有 这 些 将 在 下 一 节 说 明 。 这 种 中 间 级别 经 常 留 给 编 译 器 , 尽 管 程 序 员 也 可 以 随 意 介 入 , 例 如 , 程 序 员 可 能 会 注 意 到 , 两 个 独 立 的 循 环 可 以 在 一 个 循 环 中 实 现 ( 称 为 循 环 阻 塞 技 术 〕, 并 根 据 情 况 重 新编 写 代 码 , 典 型 的 循 环 如 下 :
for (i=0; i<10; i++) nArray1[i]=i;
for (j=0; j<10; j++)
nArray2[j]=j;
循 环 阻 塞 将 循 环 组 合 为 一 个 循 环 , 在 同 一 循 环 中 , 所 做 的 工 作 并 不 相 同 , 这 样 便节 省 了 第 2 个 循 环 的 开 销 :
for (i=0; i<10; i++)
{ nArray1[i]=i; nArray2[i]=i;
}
Visual C++ 不 能 识 别 这 样 的 循 环 阻 塞 , 因 此 , 如 果 没 有 人 为 的 干 涉 , 便 将 失 去 优化 机 会 。
当 决 定 是 否 优 化 速 度 或 者 大 小 时 , 应 该 记 住 , 速 度 的 节 省 几 乎 总 是 可 以 度 量 的 , 但 并 不 是 总 能 被 人 们 感 觉 到 。一 个 较 普 遍 的 原 因 在 于 , 计 算 机 时 钟 的 测 量 和 人 们所 能 辨 别 的 速 度 之 间 有 很 大 的 差 别 。 提 高 最 终 用 户 感 觉 不 到 的 程 序 运 行 速 度 ,无异 于 浪 费 精 力 。
- 般 来 说 , 只 有 算 法 优 化 能 使 运 行 速 度 得 以 显 著 提 高 ,
较 低 级 的 优 化 通 常 不 能 节省 几 百 万 个 时 钟 周 期 , 而 这 是 人 们 能 感 觉 到 速 度 的 提 高 所 需 要 的 , 除 非 是 应 用 优
化 的 特 定 循 环 和 或 函 数 执 行 上 百 次 。 由 于 这 个 原 因 , 在 编 程 方 面 , 人 们 已 经 发 展了 以 源 程 序 级 别 来 编 写 有 效 的 算 法 , 并 设 置 编 译 器 优 化 尺 寸 , 而 不 是 优 化 速 度 。对 于 多 任 务 操 作 系 统 , 例 如 W indows 系 统 , 特 别 鼓 励 这 种 方 式 , 在 内 存 拥 挤 的条 件 下 , 对 于 具 有 较 小 内 存 映 像 的 程 序 , 其 引 发 页 面 错 误 的 机 会 就 更 少 。 在 出 现页 面 错 误 时 , 操 作 系 统 必 须 从 磁 盘 上 重 新 加 载 内 存 , 这 种 操 作 的 代 价 是 很 昂 贵的 。 将 它 们 与 程 序 组 合 在 一 起 , 无 论 对 速 度 的 优 化 有 多 大 , 看 上 去 , 也 似 乎 没 有反 应 , 而 且 反 应 较 慢 。
优 化 技 术
Visual C++ 从 优 化 技 术 中 引 用 的 许 多 技 术 已 经 被 编 译 器 使 用 了 多 年 。表 12-1 列 出了 一 些 最 重 要 的 优 化 技 术 , Visual C++ 利 用 它 们 , 并 指 出 其 目 的 是 用 来 减 小 代 码的 尺 寸 , 提 高 代 码 速 度 , 还 是 二 者 兼 而 有 之 。 因 为 这 里 涉 及 到 许 多 变 量 , 有 时 , 要 事 先 预 测 优 化 技 术 的 全 面 影 响 是 相 当 困 难 的 。 然 而 , 表 中 所 反 应 的 仅 仅 是 编 译器 的 意 图 , 而 不 是 必 要 的 结 果 。对 特 殊 程 序 的 最 佳 优 化 设 置 经 常 通 过 试 错 法 来 决定 。
在 这 里 , 我 们 开 始 给 出 一 系 列 简 短 的 子 程 序 段 , 来 检 查 表 12-1 中 所 列 的 14 种 优化 方 法 。 每 个 子 程 序 段 描 述 了 优 化 是 怎 样 工 作 的 , 什 么 时 候 使 用 它 , 它 的 优 缺 点是 什 么 。
处 理 器 寄 存 器 的 使 用
在 以 往 的 C 程 序 编 制 中 , 较 好 的 方 法 是 利 用 Register 关 键 字 ,使 一 或 两 个 函 数 的局 部 变 量 成 为 寄 存 器 变 量 。 寄 存 器 存 储 类 反 应 了 程 序 员 的 一 个 要 求 ,即 告 诉 编 译器 , 如 果 有 一 个 可 用 的 寄 存 器 变 量 , 就 不 将 它 分 配 在 堆 栈 中 , 而 在 处 理 器 寄 存 器中 保 持 局 部 变 量 。 除 了 节 省 少 量 的 堆 栈 空 间 外 , 在 寄 存 器 里 保 持 变 量 , 可 以 保 证访 问 它 的 速 度 最 快 ,因 为 处 理 器 读 写 自 己 的 寄 存 器 要 比 读 写 内 存 快 得 多 。在 寄 存器 中 管 理 一 个 变 量 ( 而 不 是 在 内 存 中 ) 也 能 导 致 代 码 大 小 稍 有 下 降 。
表 12-1 Visual C++ 编 译 器 优 化 技 术
优 化 |
减 小 大 小 |
提 高 速 度 |
---|---|---|
处 理 器 寄 存 器 的 使 用 |
|
|
常 量 传 播 和 复 制 传 递 |
|
|
死 码 和 死 存 储 区 的 消 除 |
|
|
普 通 子 表 达 式 的 消 除 |
|
|
循 环 优 化 |
|
|
指 令 计 划 强 度 减 少 |
|
|
内 嵌 扩 展 字 符 串 合 并 |
|
|
禁 用 堆 栈 检 查 |
|
|
续 表
结 构 指 针 的 删 除
堆 栈 覆 盖
假 定 没 有 使 用 别 名
函 数 级 别 链 接
今 天 , 人 们 很 少 看 到 有 人 使 用 register 变 量 , 因 为 优 化 的 编 译 器( 像 Visual C++ ) 能 自 动 处 理 这 项 任 务( 实 际 上 , Visual C++ 忽 略 了 Registry 关 键 字 )。 几 乎 所 有 的数 据 对 象 都 可 以 作 为 寄 存 器 对 象 , 例 如 , 全 局 和 局 部 变 量 、 常 数 值 、 结 构 元 素 和函 数 参 数 , 包 括 按 引 用 传 递 的 参 数 指 针 。 编 译 器 扫 描 函 数 , 来 决 定 怎 样 使 用 它 的数 据 , 给 每 个 变 量 分 配 一 个 分 数 , 来 代 表 在 寄 存 器 中 存 储 变 量 的 好 处 。 当 写 出 函数 的 目 标 代 码 时 , 编 译 器 尽 可 能 将 得 到 最 高 分 的 变 量 置 于 寄 存 器 中 。 结 果 是 , 在适 当 的 情 况 下 , 提 高 了 运 行 速 度 。
在 计 算 机 中 , 寄 存 器 较 少 , 并 且 , 在 决 定 什 么 时 候 使 用 寄 存 器 来 存 储 变 量 时 , 编译 器 必 须 作 出 明 智 的 选 择 。 优 化 的 代 码 花 费 部 分 时 间 , 以 便 在 寄 存 器 和 内 存 之 间改 变 数 据 。 代 码 通 过 将 它 的 内 容 写 入 变 量 的 基 本 内 存 地 址 来 释 放 寄 存 器 。 但 是 , 优 化 编 译 器 必 须 首 先 决 定 是 否 值 得 内 存 访 问 。释 放 寄 存 器 ,而 后 又 再 次 用 同 一 值调 用 , 如 果 它 使 寄 存 器 可 以 用 于 更 短 的 代 码 段 , 这 样 做 也 可 能 并 不 值 得 。
常 量 传 播 和 复 制 传 播
代 码 优 化 中 的 指 导 原 则 就 是 寄 存 器 比 常 量 快 , 常 量 比 内 存 要 快 。如 果 没 有 足 够 的可 用 寄 存 器 来 容 纳 一 个 代 码 段 中 的 所 有 变 量 ,下 一 个 最 佳 选 择 方 法 就 是 用 一 个 常量 来 代 替 表 达 式 。 编 译 器 遇 到 常 量 传 播 时 可 以 使 用 常 量 , 在 这 种 情 况 下 , 指 定 的常 量 值 向 前 或 通 过 代 码 传 播 。编 译 器 通 过 替 换 表 达 式 来 优 化 代 码 , 以 求 出 一 个 常量 值 , 例 如 , 下 面 的 行 :
X = 255 ; Y = X ;
用 下 面 的 式 子 表 示 会 更 好 :
X=255 ; Y=255 ;
通 过 用 一 个 常 量 值 重 写 第 2 行 ,编 译 器 便 节 省 了 一 个 不 必 要 的 内 存 访 问 。尽 管 优化 技 术 本 身 经 常 就 叫 做 “ 传 播 ( propagation )”, 这 个 术 语 更 准 确 地 说 明 了 优 化 即意 味 着 修 正 。
复 制 传 播 类 似 于 常 量 传 播 , 在 一 系 列 的 赋 值 中 ,一 个 值 从 一 个 变 量 传 递 到 另 一 个变 量 时 , 便 会 发 生 复 制 传 播 。 在 这 种 情 形 中 , 中 间 的 变 量 不 使 用 值 , 而 只 是 将 它传 递 到 下 一 个 变 量 。 在 这 个 系 列 中 , 更 有 效 的 是 , 将 值 直 接 赋 予 最 后 的 变 量 , 并跳 过 其 他 变 量 。 这 里 有 一 个 例 子 , 在 此 , 删 除 复 制 传 播 , 来 给 出 一 个 无 关 紧 要 的语 句 , 编 译 器 作 出 了 一 个 简 单 的 替 换 。 看 看 这 些 代 码 序 列 :
i= nParam;
Function(i);
i=j;
转 变 为 :
i= nParam; Function(nParam); i=j;
在 这 里 , nParam 值 通 过 i 来 传 播 , 以 便 变 成 function 的 参 数 , 但 是 , 既 然 i 从 来不 使 用 nParam 值 ,复 制 传 播 是 没 有 必 要 的 。编 译 器 能 较 安 全 地 将 nParam 替 换 为函 数 参 数 。 因 为 这 种 优 化 , 第 1 个 赋 值 语 句 现 在 变 成 了 无 用 的 “ 死 存 储 区 ”, 在下 一 节 将 会 讨 论 这 一 点 。
死 存 储 区 和 死 码 的 消 除
当 我 们 看 到 前 面 的 例 子 时 , 复 制 传 播 经 常 将 中 间 赋 值 语 句 留 作 死 存 储 区 , 程 序 可以 将 数 据 赋 给 一 个 变 量 , 而 无 需 从 中 读 取 它 。 当 它 识 别 出 死 存 储 区 赋 值 时 , 优 化编 译 器 只 是 忽 略 这 些 指 令 , 以 便 它 不 会 变 成 目 标 映 象 中 的 一 部 分 。 例 如 , 在 编 译器 消 除 了 死 存 储 区 后 , 原 来 此 段 中 的 三 个 指 令 被 减 少 为 两 个 指 令 。
Function (nParam) i = j
在 编 译 器 扩 充 了 复 杂 的 宏 后 , 便 可 以 删 除 复 制 传 播 和 死 存 储 区 了 。
与 死 存 储 区 相 关 的 是 死 码 。死 码 是 程 序 运 行 时 处 理 器 不 能 到 达 的 一 条 指 令 或 一 群指 令 ,这 些 不 可 达 到 的 代 码 经 常 是 目 前 优 化 的 副 产 品 , 由 于 编 译 器 没 有 为 死 码 或死 存 储 区 产 生 目 标 指 令 , 因 此 , 消 除 这 些 条 件 代 表 了 最 理 想 的 优 化 。
公 共 子 表 达 式 的 消 除
当 编 译 器 识 别 到 一 系 列 表 达 式 都 影 响 相 同 的 值 时 ,它 会 计 算 一 次 子 表 达 式 , 并 替换 系 列 中 所 有 子 表 达 式 的 结 果 。 例 如 , 考 虑 下 段 中 的 子 表 达 式 y*z:
x=y*z; w=y*z;
通 过 添 加 一 个 赋 值 语 句 , 并 用 一 个 变 量 替 代 两 个 子 表 达 式 ,编 译 器 消 除 了 两 个 多相 操 作 中 的 一 条 。
temp=y*z; x=temp;
w=temp;
根 据 环 境 以 及 子 表 达 式 是 否 经 常 出 现 , 替 换 可 以 减 少 程 序 段 中 的 代 码 大 小 。 而且 , 消 除 公 共 的 子 表 达 式 几 乎 总 能 提 高 运 行 速 度 。
循 环 优 化
循 环 内 部 的 优 化 通 常 是 显 著 有 效 的 , 因 为 速 度 是 通 过 循 环 的 次 数 来 加 倍 提 高 。在
以 前 程 序 段 里 所 描 述 的 优 化 仅 仅 当 应 用 是 在 循 环 内 的 代 码 时 才 会 更 有 效 。 但 是 , 这 里 也 有 其 他 的 优 化 技 术 , 适 用 于 针 对 循 环 的 编 译 器 。 也 许 , 最 常 见 的 循 环 优 化技 术 就 是 不 变 式 移 动 或 提 升 , 这 意 味 着 将 代 码 从 一 个 循 环 内 移 到 循 环 外 ,“ 不变 ” 是 指 , 在 通 过 所 有 的 循 环 时 , 一 个 表 达 式 仍 保 持 常 量 值 , 这 里 有 一 个 典 型 的例 子 , 它 是 循 环 中 的 不 变 表 达 式 :
for (i=0; i<10; i++) nArray[i]=x+y ;
通 过 从 循 环 中 移 出 不 变 表 达 式 ,编 译 器 产 生 仅 计 算 一 次 表 达 式 的 代 码 ,而 不 是 10
次 , 在 代 码 大 小 上 没 有 明 显 的 增 大 。
temp=x+y; for(i=0; i<10; i++)
nArray[i]=temp;
指 令 顺 序 安 排
如 果 一 条 指 令 不 依 赖 于 其 他 指 令 的 结 果 , 高 级 处 理 器( 像 奔 腾 系 列 ) 能 够 在 两 个类 似 的 流 水 线 同 时 执 行 两 条 指 令 。独 立 性 可 能 会 导 致 称 为 流 水 线 堆 栈 的 条 件 。通
过 使 用 指 令 时 序 安 排 , 也 就 是 指 令 定 序 , 编 译 器 通 过 重 新 安 排 机 器 指 令 的 顺 序 , 以 阻 止 这 样 的 独 立 性 产 生 。 例 如 , 考 虑 标 为 A , B , C 的 三 个 指 令 :
add ax,iShort ;Instruction A movsx ebx,ax ; Instruction B
xor ecx,ecx ; Instruction C
指 令 A 和 B 不 能 同 时 执 行 , 因 为 指 令 B 依 赖 于 指 令 A 的 结 果 。 也 就 是 说 , 在 运行 指 令 B 之 前 , 处 理 器 必 须 了 解 寄 存 器 AX 中 的 值 和 它 的 符 号 位 状 态 。而 且 ,指令 C 既 不 依 赖 于 A , 也 不 依 赖 于 B , 通 过 颠 倒 指 令 B 和 指 令 C 的 顺 序 , 编 译 器便 避 免 了 潜 在 的 拖 延 情 况 的 发 生 , 允 许 指 令 A 和 指 令 C 同 时 执 行 :
add ax,iShort ; Instruction A xor ecx,ecx ; Instruction C
movsx ebx,ax ; Instruction B
指 令 顺 序 安 排 对 代 码 的 大 小 无 多 大 的 影 响 , 并 且 ,仅 当 程 序 在 高 级 处 理 器 上 运 行时 才 会 发 生 作 用 。 在 此 章 的 最 后 一 节 中 , 对 指 令 顺 序 安 排 有 较 详 细 的 说 明 。
降 低 强 度
处 理 器 在 做 加 法 和 减 法 时 比 较 快 , 在 做 乘 法 和 除 法 时 , 相 对 要 慢 些 。 例 如 , 奔 腾处 理 器 在 一 个 时 钟 周 期 中 可 以 添 加 两 个 32 位 寄 存 器 , 而 在 进 行 乘 法 时 , 需 要 10 个 周 期 , 在 做 除 法 时 , 所 需 要 的 周 期 要 超 过 40 个 。 在 优 化 时 , Visual C++ 编 译 器要 寻 求 减 少 算 术 复 杂 性 的 机 会 , 也 就 是 减 少 指 令“ 强 度 ” 的 机 会 , 而 不 影 响 计 算的 结 果 。
例 如 , 乘 以 或 除 以 2 的 指 令 强 度 可 以 通 过 等 效 的 移 位 操 作 来 实 现 。假 定 y 是 一 个无 符 号 整 数 , 对 于 如 下 操 作 :
y=y/16
编 译 器 可 以 将 它 替 换 为 :
y=y>>4;
这 种 替 代 产 生 了 与 原 始 指 令 同 样 的 结 果 , 因 为 除 以 1 6( 2 4 ) 可 以 与 右 移 4 位 有 同样 的 效 果 。 以 其 他 方 向 移 位 也 一 样 , 因 此 , 整 数 变 量 乘 以 2 n , 就 等 效 于 左 移 n 位 。
在 从 汇 编 级 别 来 观 察 时 , 这 种 优 化 要 更 有 趣 , 这 里 给 出 了 反 汇 编 时 原 始 指 令 的 状态 , 每 条 机 器 指 令 使 用 的 时 间 在 注 释 中 给 出 :
// Instructions for y = y/16
mov |
ecx, 16 |
; 1 cycle on a Pentium |
---|---|---|
mov |
eax dword ptr [y] |
; 1 cycle |
cdq idiv |
ecx |
; 3 cycles ;46 cycles |
mov |
dword ptr [y], eax |
; 1 cycle ;52 cycles total |
强 度 减 少 可 以 利 用 一 条 简 单 的 指 令 替 代 :
// Instruction for y=y>>4;
sar dword ptr [y],4 ;3 cycles total
在 Visual C++ 编 译 器 里 , 这 个 例 子 颇 具 学 术 价 值 。 用 等 效 的 移 位 指 令 来 替 代 乘 或除 操 作 , 其 改 进 的 效 果 相 当 明 显 , Visual C++ 即 使 在 优 化 关 闭 时 也 会 作 出 这 样 的替 换 。
内 嵌 扩 展
为 什 么 调 用 一 个 函 数 的 行 为 会 使 程 序 执 行 流 的 进 程 减 慢 , 原 因 有 几 个 。因 为 处 理器 会 跳 到 代 码 的 新 位 置 , 处 理 器 指 令 队 列 中 保 存 的 即 将 调 用 的 指 令 就 不 再 有 用了 。 如 果 处 理 器 不 执 行 二 分 法 预 测 ( 奔 腾 机 也 是 一 样 〕, 在 这 个 队 列 满 时 , 并 且函 数 的 第 一 个 指 令 从 内 存 中 检 索 到 时 , 它 就 会 停 下 来 。 更 糟 糕 的 是 , 当 函 数 的 参数 被 推 到 堆 栈 上 和 处 理 器 的 EIP 寄 存 器 上 时 ,此 调 用 也 许 会 产 生 一 系 列 的 内 存 写操 作( 对 EIP 寄 存 器 的 描 述 , 参 见 第 11 章 中 有 关 调 试 器 的 附 加 信 息 )。 当 函 数 完成 后 , 当 返 回 地 址 从 堆 栈 中 弹 出 到 EIP 寄 存 器 中 时 , 处 理 器 会 再 次 停 止 , 如 果 有必 要 的 话 , 预 取 的 队 列 会 被 填 满 , 并 且 , EIP 指 向 的 下 一 指 令 将 从 内 存 中 读 取 。简 而 言 之 , 调 用 函 数 和 退 出 函 数 的 代 价 都 相 当 昂 贵 。
内 嵌 扩 展 可 以 解 决 这 些 问 题 ,但 有 时 会 以 增 大 代 码 大 小 为 代 价 。在 这 个 优 化 技 术里 , 编 译 器 将 函 数 码 插 入 程 序 的 主 体 里 , 用 一 个 函 数 自 己 的 复 制 版 本 来 代 替 函 数调 用 。 永 远 也 不 生 成 CALL 机 器 指 令 , 使 处 理 器 可 以 沿 着 指 令 的 顺 序 路 径 进 行 , 而 不 会 转 到 其 他 地 方 。 按 照 顺 序 的 逻 辑 路 径 , 处 理 器 能 够 更 精 确 地 预 取 指 令 , 当内 嵌 扩 展 发 生 在 一 个 循 环 中 时 , 节 省 的 时 间 会 更 多 。
看 上 去 似 乎 很 奇 怪 ,但 内 嵌 扩 展 经 常 可 以 降 低 程 序 的 大 小 。内 嵌 扩 展 在 应 用 到 较小 的 函 数 时 会 更 有 效 , 尤 其 是 对 于 参 数 是 常 量 ,或 者 按 指 针 传 递 而 不 是 按 值 来 传递 的 参 数 。在 这 些 情 况 下 ,编 译 器 可 以 不 需 要 整 段 代 码 ,便 将 参 数 值 写 到 堆 栈 上 。内 嵌 函 数 能 够 节 省 程 序 段 的 起 始 和 结 束 部 分 , 并 可 以 省 却 创 建 独 立 的 堆 栈 结 构 。
内 嵌 也 暴 露 了 函 数 的 副 作 用 , 例 如 , 改 变 为 全 局 变 量 , 它 们 对 编 译 器 来 说 是 不 可见 的 。 这 可 以 进 行 更 多 的 其 他 优 化 , 可 能 无 需 内 嵌 扩 展 也 行 。
字 符 串 合 并
编 译 器 能 决 定 什 么 时 候 程 序 多 次 创 建 相 同 的 字 符 串 。 字 符 串 合 并 是 一 种 优 化 技术 , 在 这 种 技 术 中 , 编 译 器 仅 仅 为 第 1 个 字 符 串 分 配 数 据 空 间 , 然 后 将 指 针 重 新指 向 第 一 个 字 符 串 的 任 何 复 制 字 符 串 。
堆 栈 结 构 指 针 的 忽 略
堆 栈 结 构 指 针 的 忽 略 对 Intel 系 统 来 说 是 一 种 优 化 , 它 能 节 省 代 码 的 起 始 和 结 尾代 码 , 对 拥 有 许 多 函 数 的 程 序 来 说 , 这 种 节 省 是 可 观 的 。 如 果 没 有 堆 栈 结 构 指 针的 忽 略 ,编 译 器 会 为 每 一 个 要 求 堆 栈 结 构 的 函 数 产 生 序 言 代 码 。将 处 理 器 的 EBP 的 寄 存 器 指 向 堆 栈 结 构 的 顶 端 , 如 下 :
push ebp ;Save EBP register
mov ebp, esp ;Point to top of frame sub esp,local_space ;Allocate stack frame
当 函 数 完 成 时 , 结 尾 代 码 消 除 堆 栈 结 构 :
mov esp,ebp ;Restore stack pointer
pop ebp ;Restore EBP register
利 用 这 种 方 法 ,EBP 寄 存 器 被 堆 栈 结 构 指 针 所 调 用 ,自 动 内 存 类 变 量 在 堆 栈 结 构中 通 过 相 对 于 EBP 的 偏 移 量 而 被 引 用 ,利 用 EBP 作 为 结 构 指 针 是 W indows 老 版本 中 遗 留 下 来 的 ,它 被 设 计 在 Intel 80286 处 理 器 上 运 行 。当 结 构 指 针 的 忽 略 起 作用 时 , 编 译 器 根 据 ESP 寄 存 器 的 相 对 堆 栈 数 据 , 而 不 是 EBP 寄 存 器 , 来 引 用 堆栈 数 据 。 函 数 的 序 言 变 成 一 个 简 单 的 指 令 , 来 调 整 ESP 堆 栈 指 针 创 建 堆 栈 结 构 :
sub esp,local_space ;Allocate stack frame
结 尾 程 序 会 去 除 堆 栈 结 构 , 方 法 是 将 local_space 添 加 到 ESP , 更 好 的 是 , 堆 栈 指针 的 忽 略 使 EBP 寄 存 器 可 以 自 由 地 用 在 其 他 优 化 上 ,结 构 指 针 忽 略 的 弊 端 在 于 , 要 编 码 相 对 于 ESP 的 内 存 指 针 , 这 比 相 对 于 EBP 的 相 同 指 针 多 用 一 个 字 节 。
禁 用 堆 栈 检 查
W in32 里 的 堆 栈 检 查 与 16 位 环 境 不 一 样 。 在 16 位 W indows 中 , 堆 栈 检 查 涉 及到 调 用 一 个 C 运 行 函 数 , 即 堆 栈 探 测 器 ( stack probe )。 在 一 个 程 序 中 , 在 每 个函 数 的 开 始 进 行 调 用 , 堆 栈 探 测 器 确 定 这 个 堆 栈 是 否 有 足 够 的 空 间 ,来 适 应 函 数的 自 动 存 储 要 求 。 如 果 堆 栈 空 间 足 够 , 探 测 器 返 回 , 并 且 函 数 继 续 运 行 。 否 则 , 探 测 器 会 通 知 开 发 人 员 函 数 不 能 执 行 , 因 为 它 超 出 了 堆 栈 的 限 度 。
W in32 应 用 程 序 不 要 求 这 种 堆 栈 检 查 , 因 为 有 一 种 系 统 服 务 阻 止 了 堆 栈 溢 出 。 当一 个 程 序( 或 线 程 ) 访 问 堆 栈 底 端 附 近 的 内 存 时 , 操 作 系 统 假 设 堆 栈 空 间 已 经 不够 用 了 ,并 且 相 应 提 高 堆 栈 的 大 小 。这 将 在 程 序 的 最 深 访 问 处 和 堆 栈 的 底 端 之 间产 生 更 大 的 距 离 。 尽 管 自 动 堆 栈 调 整 使 老 式 的 16 位 堆 栈 探 测 器 过 时 , 堆 栈 检 查仍 然 可 以 应 用 在 W in32 应 用 程 序 里 。 为 了 了 解 这 种 目 的 , 有 必 要 检 查 系 统 怎 样将 内 存 添 加 到 堆 栈 上 。
对 一 个 应 用 程 序 或 单 独 的 线 程 来 说 , 堆 栈 空 间 在 页 面 提 交 。页 面 大 小 取 决 于 目 标系 统 ,对 Intel、M IPS 和 PowerPC 系 统 来 说 ,一 页 有 4KB 。仅 当 堆 栈 的 访 问 结 束 , 进 入 到 保 护 页 的 区 域 后 , 操 作 系 统 才 能 识 别 堆 栈 溢 出 。保 护 页 是 堆 栈 中 最 后 的 提交 页 ( W indows NT 中 设 置 的 保 护 页 与 W indows 95 中 略 有 不 同 )。 当 程 序 访 问 保护 页 中 的 堆 栈 内 存 时 , 系 统 将 提 交 另 一 页 , 来 增 加 堆 栈 的 大 小 , 此 过 程 称 为“ 堆栈 的 增 长 ”。 图 12-1 显 示 了 堆 栈 是 怎 样 通 过 被 操 作 系 统 提 交 的 页 而 增 长 的 。
如 图 12-1 所 示 , 对 一 个 应 用 程 序 来 说 , 超 出 堆 栈 保 护 页 , 并 试 图 进 入 保 留 的 内存 , 这 是 可 能 发 生 的 。这 可 能 发 生 在 函 数 为 其 局 部 变 量 分 配 更 多 的 堆 栈 页 面 的 情况 下 :
void BigLocal ( )
{
char chArray [3*4096]; //Allocate 3 pages (12 kb) of stack
chArray[12000] = -1; //This assignment may fail
.
.
.
}
在 这 个 简 单 的 说 明 中 , chArray 假 定 3 页 (12KB ) 的 堆 栈 。 函 数 为 自 动 数 据 分 配 空间 , 方 法 是 按 请 求 的 12KB 来 减 少 处 理 器 的 堆 栈 指 针 ESP , 但 是 , 这 不 可 能 提 交
更 多 的 堆 栈 。 如 果 分 配 给 chArray 的 空 间 在 靠 近 堆 栈 的 底 端 进 行 , 访 问 chArray 中 的 高 序 元 素 可 能 超 过 堆 栈 的 保 护 页 ,从 而 进 入 保 留 的 内 存 。这 将 触 发 一 种 非 法的 访 问 操 作 , 系 统 会 通 过 终 止 应 用 程 序 来 解 决 这 种 系 统 问 题 。
图 12-1 应用程序堆栈的增长
在 W in32 的 堆 栈 检 查 中 , 可 以 阻 止 这 种 类 型 的 方 案 。 当 启 用 堆 栈 检 查 时 , 编 译器 计 算 每 个 函 数 局 部 数 据 的 总 尺 寸 , 带 局 部 变 量 的 函 数 消 耗 的 堆 栈 页 面 要 少 一个 , 不 能 超 出 保 护 页 , 并 因 此 不 能 要 求 堆 栈 检 查 。 而 且 , 带 有 多 页 自 动 数 据 的 每个 函 数 要 优 先 于 在 C 运 行 库 中 的 堆 栈 检 查 调 用 。 堆 栈 检 查 例 程 只 是 到 达 接 下 来
的 堆 栈 页 , 也 就 是 说 , 它 进 入 一 个 循 环 , 来 读 取 堆 栈 上 的 字 节 , 其 增 量 为 4096 字 节 。 循 环 在 堆 栈 的 顶 端 开 始 , 并 向 下 继 续 执 行 , 直 到 堆 栈 检 查 例 程 到 达 足 够 多的 页 , 来 满 足 函 数 的 堆 栈 要 求 。
图 12-1 显 示 了 堆 栈 检 查 怎 样 解 决 BigLocal 函数的问题,在 BigLocal 函 数 得 到 控制 权 之 前 , 堆 栈 检 查 例 程 到 达 堆 栈 顶 端 下 面 的 1 , 2 和 3 页 。 假 定 chArray 的 分配 在 堆 栈 中 的 最 后 提 交 页 中 开 始 , 第 一 次 到 达 将 访 问 保 护 页 ,系 统 的 反 应 是 提 交另 一 页 , 使 它 成 为 新 的 保 护 页 。 在 堆 栈 检 查 例 程 中 , 循 环 里 的 第 2 次 重 复 到 达 新的 保 护 页 , 使 得 系 统 提 交 下 一 页 。 此 处 理 会 第 3 次 重 复 , 在 堆 栈 检 查 例 程 返 回 和BigLocal 得 到 控 制 之 前 , 给 堆 栈 增 加 三 页 。 现 在 , 当 BigLocal 存 取 邻 近 chArray 末 尾 时 的 一 个 元 素 时 , 存 取 陷 入 堆 栈 的 提 交 页 中 , 并 且 不 会 触 发 错 误 。
BigLocal 没 有 堆 栈 检 查 也 能 解 决 它 自 己 的 问 题 吗 ? 绝 对 可 以 。 例 如 , 只 要 在Array[12000] 之 前 访 问 chArray[4000] 和 chArray[8000], 函 数 便 可 以 照 管 提 交 要 求的 内 存 , 并 确 保 堆 栈 没 有 溢 出 。 堆 栈 检 查 给 程 序 添 加 了 开 销 , 禁 用 它 可 以 节 省 代码 , 并 且 , 对 于 具 有 很 大 的 存 储 器 要 求 的 应 用 程 序 , 可 以 增 强 其 运 行 速 度 。 只 要在 随 后 的 页 中 存 取 堆 栈 数 据 ,并 从 堆 栈 的 顶 部 向 底 部 工 作 ,这 些 应 用 程 序 不 要 求进 行 堆 栈 检 查 。
堆 栈 覆 盖
堆 栈 覆 盖 优 化 也 许 有 好 处 , 也 许 没 有 好 处 。 这 取 决 于 堆 栈 适 用 的 范 围 。 通 过 使 用堆 栈 覆 盖 , 编 译 器 重 新 使 用 堆 栈 空 间 , 来 保 存 没 有 覆 盖 的 局 部 变 量 , 这 意 味 着 ,
在 一 个 函 数 中 , 如 果 最 后 访 问 X 在 第 一 次 访 问 Y 之前,则 X 和 Y 均 能 安 全 地 占据 堆 栈 结 构 的 同 一 位 置 。
通 过 最 大 限 度 的 地 减 小 所 占 据 堆 栈 的 深 度 ,编 译 器 降 低 程 序 运 行 时 堆 栈 溢 出 的 机会 。 尽 管 堆 栈 增 大 时 的 系 统 反 应 对 用 户 来 说 是 透 明 的 , 但 操 作 会 消 耗 不 少 时 间 , 堆 栈 覆 盖 也 能 减 少 一 个 函 数 的 运 行 大 小 ,方 法 是 缩 短 堆 栈 上 的 局 部 变 量 与 堆 栈 结构 顶 端 之 间 的 距 离 。重 新 调 用 函 数 的 结 构 指 针 指 向 堆 栈 结 构 的 顶 端 。如 果 在 一 个堆 栈 中 ,局 部 变 量 所 占 据 的 位 置 距 离 堆 栈 结 构 指 针 少 于 128 个 字 节 ,则 编 码 每 个变 量 的 指 针 时 , 要 比 偏 移 量 大 于 128 字 节 时 少 用 3 个 以 下 的 字 节 。
mov |
eax,[EBP+4] |
; This instruction is 3 bytes smaller |
---|---|---|
mov |
eax,[EBP+256] |
; than this instruction |
任 何 在 堆 栈 上 从 覆 盖 变 量 所 得 到 的 好 处 都 与 环 境 有 关 , 但 是 ,堆 栈 覆 盖 没 有 任 何代 价 。
假 定 没 有 别 名
别 名 意 味 着 使 用 多 个 名 字 引 用 一 个 内 存 对 象 。指 针 和 联 合 给 程 序 员 提 供 了 众 多 的机 会 来 使 用 别 名 。 下 面 给 出 了 一 个 比 较 典 型 的 例 子 , 其 中 , c 和 *cptr 引 用 内 存 中的 相 同 字 节 。
char c;
char *cptr = &c;
别 名 的 使 用 限 制 了 编 译 器 进 行 某 种 优 化 的 能 力 , 如 将 变 量 设 为 寄 存 器 变 量 。 例如 , 在 这 种 代 码 段 中 , 编 译 器 不 能 安 全 地 存 储 寄 存 器 中 的 变 量 , 如 果 有 这 种 可 能性 , 程 序 随 后 将 用 cptr 而 不 是 c 将 一 个 新 值 写 入 内 存 。 如 果 发 生 这 样 的 情 况 , 寄存 器 内 的 值 将 不 再 有 效 。 编 译 器 能 经 常 较 成 功 地 跟 踪 cptr 和 c 的使用,而且,不管 别 名 的 使 用 如 何 , 在 安 全 的 情 况 下 , 它 可 以 重 新 将 C 作 为 寄 存 器 变 量 ( cptr 变 量 在 任 何 情 况 下 均 能 被 再 次 寄 存 )。
别 名 可 以 假 定 一 种 微 妙 的 格 式 ,使 编 译 器 不 能 识 别 , 接 下 来 的 代 码 描 述 了 两 个 变量 ptr1 和 ptr2 均 指 向 同 一 数 组 的 情 况 。 然 而 , 编 译 器 不 能 识 别 这 个 别 名 , 因 为ptr2 是 从 主 函 数 作 用 域 之 外 的 另 一 个 函 数 中 获 得 它 的 值 。
char chArray[5]; // Global scope
main ()
{
char *ptr1 = chArray; //ptr1 points to chArray
char *ptr2 = GetPointer(); //So does ptr2
}
char * GetPointer (void)
{
return chArray;
}
当 以 保 守 的 方 式 进 行 优 化 时 ,以 上 例 子 能 够 正 确 运 行 , 因 为 编 译 器 不 能 事 先 知 道ptr2 的 值 , 它 允 许 这 种 可 能 性 存 在 ,即 ptr1 和 ptr2 均 是 同 一 内 存 目 标 的 别 名 。 因此 , 它 可 能 在 涉 及 到 其 中 任 何 一 个 指 针 时 不 进 行 任 何 优 化 。这 种 假 设 是 比 较 安 全的 , 但 也 可 能 会 引 起 编 译 器 错 过 合 理 优 化 的 机 会 。
Visual C++ 编 译 器 提 供 了 一 个 Assume No Aliasing ( 假 定 没 有 别 名 ) 开 关 来 处 理类 似 这 样 的 情 形 。这 种 优 化 开 关 提 示 编 译 器 变 量 没 有 隐 藏 的 别 名 , 与 本 例 子 中 的chArray 的 情 形 一 样 。 此 开 关 给 编 译 器 提 供 了 一 种 权 限 , 去 主 动 优 化 涉 及 指 针 的
代 码 , 这 些 指 针 通 过 不 可 见 的 别 名 来 释 放 。
相 对 于 Assume No Aliasing 选 项 , Visual C++ 还 提 供 了 不 太 主 动 的 方 式 , 名 为Assume Aliasing Across Function Calls ( 假 定 别 名 跨 越 函 数 调 用 )。 这 种 优 化 开 关告 诉 编 译 器 , 假 定 别 名 在 代 码 中 不 存 在 , 除 非 是 跨 越 函 数 调 用 。 此 开 关 给 予 编 译器 的 仅 仅 是 限 定 的 权 限 , 来 优 化 涉 及 到 指 针 的 代 码 , 但 是 , 这 比 根 本 没 有 权 限 要更 好 一 些 。本 章 的 后 一 节 将 会 讨 论 这 两 个 优 化 开 关 经 常 是 怎 样 应 用 于 试 错 方 法 中的 。
函 数 级 别 的 链 接
对 一 个 函 数 而 言 , 在 它 外 面 对 它 进 行 优 化 是 可 能 的 , 可 以 通 过 内 嵌 方 法 , 或 者 因为 编 译 器 已 经 知 道 了 怎 样 用 这 种 从 不 调 用 函 数 的 方 式 来 编 译 程 序 。而 且 , 因 为 编译 器 无 法 决 定 是 否 有 别 的 源 模 块 访 问 此 函 数 , 函 数 必 须 要 被 编 译 , 并 包 含 在 目 标映 象 中 。仅 当 链 接 器 能 识 别 何 时 函 数 在 一 个 程 序 中 保 持 不 引 用 时 。 如 果 编 译 器 以压 缩 形 式 写 下 了 一 个 未 引 用 的 函 数 , 链 接 器 将 从 所 完 成 的 可 执 行 程 序 中 删 除 它 。
函 数 级 别 的 链 接 确 保 在 一 个 源 模 块 里 的 所 有 函 数 都 封 装 起 来 。也 就 是 说 , 在 目 标代 码 中 , 通 过 一 个 COMDAT 记 录 来 识 别 。 COMDAT 记 录 的 格 式 是 COFF
( Common Object File Forma t,通 用 目 标 文 件 格 式 ),含 有 允 许 链 接 器 去 识 别 未 引用 函 数 , 并 从 可 执 行 映 象 中 删 除 它 们 , 这 个 过 程 叫 做 “ 传 递 COMDAT 消 除 ”。如 果 没 有 COMDAT 记 录 , 在 链 接 后 , 映 象 中 将 保 留 未 引 用 的 函 数 , 从 而 占 据 空间 。
优 化 开 关
现 在 , 让 我 们 从 概 述 转 向 细 则 。 本 节 主 要 是 将 目 前 所 学 的 编 译 器 优 化 同 Visual C++ 中 控 件 优 化 进 程 的 开 关 联 系 起 来 。开 关 在 图 12-2 所 示 的 Project Settings 对 话框 中 , 可 以 使 用 Project 菜 单 中 的 Settings 命 令 来 调 用 它 。 Project Settings 对 话 框具 有 许 多 开 关 和 选 项 , 它 们 可 以 影 响 建 立 的 进 程 和 最 终 可 执 行 程 序 的 效 率 。这 节集 中 在 对 话 框 的 C/C++ 选 项 卡 上 ,其 中 包 含 有 用 来 管 理 编 译 器 如 何 优 化 项 目 源 文件 的 一 切 开 关 。
默 认 优 化 设 置 取 决 于 建 立 的 目 标 。 在 建 立 调 试 版 本 时 , Visual C++ 会 关 闭 优 化 , 以 确 保 可 执 行 程 序 真 正 转 换 的 是 源 程 序 。 对 于 发 行 版 本 , 默 认 情 况 下 , 编 译 器 优化 速 度 ,甚 至 不 惜 增 大 代 码 尺 寸 。对 许 多 项 目 来 说 ,默 认 优 化 设 置 是 可 以 接 受 的 , 并 且 ,在 本 书 中 已 经 用 实 例 说 明 了 这 一 点 , 你 可 以 较 轻 松 地 建 立 和 开 发 一 个 无 需进 入 Project Settings 对 话 框 的 项 目 。 我 们 将 会 明 白 , 为 什 么 需 要 人 工 调 节 项 目 的优 化 设 置 。
图 12-2 显 示 了 含 有 一 列 项 目 源 文 件 的 Project Settings 对 话 框 的 左 半 部 分 ,它 类 似于 Workspace( 工 作 空 间 ) 窗 口 里 的 FileView 窗 格 。 在 设 置 优 化 开 关 之 前 , 从 列表 顶 端 选 择 项 目 名 或 单 独 的 文 件 。 为 了 选 择 一 组 文 件 , 当 按 下 Ctrl 键 时 , 单 击 需要 的 文 件 名 。文 件 列 表 中 的 选 择 指 出 优 化 开 关 将 应 用 在 哪 个 文 件 或 文 件 组 。选 择项 目 名 , 使 优 化 设 置 对 于 所 有 源 文 件 是 通 用 的 。 选 择 单 独 的 模 块 , 可 以 为 最 快 速度 而 优 化 其 中 的 一 些 , 有 些 是 为 了 最 小 的 尺 寸 ,还 有 一 些 是 为 了 所 选 择 的 混 合 优
化 标 准 。 初 始 的 建 立 目 标 在 对 话 框 的 左 上 角 显 示 , 它 取 决 于 当 前 项 目 的 有 效 配置 。 当 设 置 编 译 器 优 化 时 , 目 标 应 该 是 Win32 发行版本。在 Project Settings 对 话框 中 选 择 目 标 并 不 能 改 变 项 目 的 有 效 配 置 。
图 12-2 在 Project Settings 对话框中选择优化目标的快捷方式
为 了 更 细 致 地 进 行 优 化 , 可 在 源 代 码 的 关 键 位 置 插 入 optimize 。 这 会 为 单 独 的 函
数 设 置 编 译 器 优 化 开 关 , 从 而 忽 略 目 前 的 项 目 设 置 , 你 可 以 优 化 特 定 函 数 的 运 行速 度 , 例 如 , 当 为 代 码 的 大 小 优 化 源 模 块 的 余 下 部 分 时 , 参 考 在 线 帮 助 , 以 寻 求更 多 的 关 于 optimize 的 信 息 。
Project Settings 对 话 框 中 的 C/C++ 选 项 卡 的 外 观 取 决 于 对 话 框 顶 部 的 Category 框中 的 当 前 选 项 。 在 这 个 框 中 已 列 出 8 种 编 译 器 分 类 , 四 种 含 有 所 有 的 与 编 译 器 优化 相 关 的 开 关 。
-
General category ( 通 用 分 类 ): 对 于 总 的 优 化 目 标 ,
这 是 很 方 便 的 选择 , 它 没 有 针 对 单 独 的 优 化 方 法 的 微 调 使 用 的 控 件 。
-
Code Generation category ( 代 码 生 成 分 类 ): 处 理 器 特
定 的 优 化 和 项目 的 默 认 调 用 约 定 。
-
Customize category ( 自 定 义 分 类 ): 字 符 串 共 用 和 函
数 级 别 的 链 接 。
-
Optimizations category ( 优 化 分 类 ): 对 项 目 优 化 进 行
微 调 。
在 C/C++ 选 项 卡 中 , 所 有 分 类 都 有 Reset( 重 新 设 置 ) 按 钮 , 而 且 , 它 是 返 回 到编 译 器 的 默 认 设 置 的 方 便 方 式 。 当 所 有 开 关 被 设 置 为 默 认 值 时 , Reset 按 钮 被 禁用 , 只 有 在 任 意 分 类 中 作 出 某 种 变 化 时 , 它 才 会 变 成 有 效 的 。 单 击 Reset 按 钮 可以 恢 复 所 有 分 类 的 默 认 设 置 , 而 不 只 是 可 见 的 分 类 。
通 用 分 类
在 通 用 分 类 中 ,你 可 以 快 速 从 几 种 名 为 Defau l (t 默 认 ), D isabl e( 禁 用 ), M aximize
Speed ( 最 大 化 速 度 ) , M inimize Size( 最 小 化 大 小 ) 和 Customize ( 自 定 义 )( 图12-2 ) 的 优 化 设 置 中 选 择 。 因 为 Disable 设 置 代 表 了 完 全 抑 制 所 有 编 译 器 优 化 的唯 一 方 式 , 它 用 于 进 行 调 试 。 Default 设 置 清 除 了 所 有 优 化 开 关 , 包 括 D isable 开关 ,这 意 味 着 ,编 译 器 还 可 执 行 一 些 提 高 速 度 的 优 化 ,稍 候 ,我 们 将 介 绍 其 详 情 。Default 设 置 不 是 特 别 有 效 。 只 有 在 想 手 工 控 制 字 符 串 共 用 和 函 数 级 别 链 接 优 化的 开 关 时 , 才 选 择 Customize 设 置 。 这 些 开 关 出 现 在 Customize 分 类 中 , 这 将 在以 后 简 单 进 行 描 述 。
在 General 分 类 中 ,最 重 要 的 优 化 设 置 是 M inimum Size 和 M aximize Speed 。这 些开 关 可 以 作 为 大 多 数( 不 是 所 有 的 ) 优 化 技 术 中 的 快 捷 方 式 , 允 许 你 选 择 优 化 目标,而无须涉及细节。表 12-2 显 示 了 M aximize Speed 和 M inimize Size 设 置 启 用的 特 定 优 化 。
M aximize Speed 和 M inimize Size 设 置 很 方 便 , 但 有 点 保 守 。 如 表 12-2 所 示 , 两种 设 置 均 不 能 启 用 别 名 优 化( Assume No Aliasing 优 化 是 Optimizations 分 类 的 一部 分 )。虽然 Maximize Speed 设 置 包 括 字 符 串 共 用 和 函 数 级 别 链 接 的 优 化 , 这 些优 化 技 术 一 般 仅 仅 改 善 代 码 的 尺 寸 , 而 不 是 速 度 。
表 12-2 通 过 Maximize Speed 和 M inim ize Size 设 置 启 用 的 优 化
优 化 最 小 尺 寸 最 快 速 度
G lobal opitimizations ( 全 局 优
化 )
Generate intrinsic function s( 产 生内 部 函 数 )
inline( 内 嵌 )
Favor small code( 支 持 小 代 码 )
Favor fast code( 支 持 快 速 代 码 )
Frame pointer omission ( 结 构 指
针 的 忽 略 )
D isable stack checkin g( 禁 用 堆 栈
检 查 )
续 表
String pooling ( 字 符 串 共 用 )
Function-level linkin g( 函 数 级 别链 接 )
目 前 , 我 们 还 没 有 遇 到 表 12-2 中 的 前 4 项 , 因 此 , 需 要 对 它 们 稍 许 解 释 一 下 。对 于 本 章 前 半 部 分 介 绍 的 某 些 编 译 器 优 化 来 说 , Global optimizations 这 个 术 语 是包 罗 万 象 的 , 例 如 窥 孔 优 化 , 处 理 器 寄 存 器 的 使 用 , 循 环 优 化 , 强 度 降 低 , 消 除不 需 要 的 元 素 , 像 死 存 储 区 和 死 码 。 表 12-1 中 所 列 的 前 8 个 编 译 器 优 化 均 在 全局 优 化 的 范 围 中 。
一 般 地 , 通 过 时 间 运 行 库 提 供 的 函 数 , 在 表 12-3 中 列 出 的 函 数 有 其 特 定 的 格 式 。编 译 器 编 写 内 部 的 内 嵌 函 数 。 也 就 是 说 , 在 选 择 Maximize Speed 选 项 时 , 没 有一 个 函 数 调 用 。 放 置 内 嵌 内 部 函 数 能 够 提 高 程 序 的 运 行 速 度 ,但 也 能 导 致 程 序 尺寸 更 大 , 这 取 决 于 程 序 使 用 内 部 函 数 的 程 度 。 例 如 , 在 Intel 处 理 器 上 运 行 的 应用 程 序 中 , strcpy 函 数 的 内 部 形 式 占 据 了 41 个 代 码 字 节 。 调 用 函 数 的 正 常 运 行版 本 最 多 占 用 18 个 字 节 , 其 中 包 括 指 令 , 来 传 递 堆 栈 上 的 两 个 字 符 串 指 针 , 以及 随 后 的 堆 栈 清 除 , 它 通 过 调 用 程 序 来 处 理 。 使 用 strcpy 函 数 的 运 行 版 本 , 而 不
是 它 的 内 在 格 式 , 这 样 可 使 随 后 大 量 使 用 此 函 数 的 应 用 程 序 代 码 大 小 大 幅 度 降低 。 当 仅 应 用 于 一 两 次 调 用 时 , 这 种 节 省 似 乎 不 太 有 用 。
表 12-3 Visual C++ 中 内 在 的 运 行 函 数
_disable _lrotr _strset exp memcp strcat
_enable strlen |
_outp |
abs |
fabs |
memcpy |
---|---|---|---|---|
_inp strcmp |
_outpw |
atan |
labs |
memset |
_inpw strcpy |
_rotl |
atan2 |
log | sin |
_lrotl |
_rotr |
cos |
log10 |
sqrt |
tan
除 了 针 对 表 12-3 中 函 数 的 内 嵌 调 用 , 转 向 内 部 优 化 也 可 以 加 速 acos, asin, cosh, fmod, pow, sinh 和 tanh 库 函 数 的 调 用 。 虽 然 这 些 函 数 并 不 是 真 正 的 内 部 函 数 , 编译 器 通 过 写 入 代 码 来 直 接 将 函 数 参 数 放 在 浮 点 芯 片 中 , 而 不 是 将 它 们 置 于 堆 栈
中 , 以 此 来 优 化 它 们 的 性 能 。 结 果 是 在 函 数 中 将 会 花 费 更 少 的 时 间 , 但 代 码 的 代价 要 提 高 些 。
当 编 译 器 遇 到 某 种 通 过 优 化 来 改 善 速 度 或 大 小 的 代 码 序 列 ,但 并 不 是 两 者 同 时 优化 时 , Favor Small Code 和 Favor Fast Code 优 化 将 影 响 编 译 器 的 决 定 。 例 如 , 考虑 一 条 指 令 , 用 71 乘 以 变 量 x , 因 为 编 译 器 不 能 通 过 简 单 的 移 位 来 完 成 乘 法 计算 , 因 此 , 当 决 定 怎 样 将 操 作 转 换 成 机 器 代 码 时 , 它 可 以 有 两 种 有 效 的 选 择 。 如下 所 示 , 第 一 种 选 择 要 慢 一 些 , 但 要 求 的 代 码 更 少 。
// Instructions for x *= 71;
mov eax, dword ptr [x] ; 1 cycle 4 bytes
imul eax,eax,71 ; 10 cycles 3 bytes
mov dword ptr [x], eax ; 1 cycles 4 bytes
; Total : 12 cycles 11 bytes
第 二 种 选 择 使 用 特 定 的 Intel 技 巧 , 来 避 免 昂 贵 的 IM U L 指 令 , 结 果 是 速 度 更 快 , 但 代 码 序 列 更 长 。
// Instructions for x *= 71;
;
- 较 这 两 个 代 码 段 的 实 际 运 行 速 度 是 比 较 困 难 的 ,因 为
使 用 时 钟 周 期 极 少 能 区 别整 个 事 件 。时 钟 周 期 所 测 定 的 仅 仅 是 处 理 器 在 运 行 一 个 指 令 时 所 花 费 的 时 间 ,不是 读 指 令 和 从 内 存 读 取 数 据 到 处 理 器 时 所 花 的 时 间 。 尽 管 就 处 理 时 间 而 言 , 第 2 种 代 码 序 列 要 比 第 1 种 代 码 序 列 快 得 多 ,严 格 来 说 , 真 正 提 高 的 速 度 可 能 要 比 这些 数 字 所 指 示 少 些 。因 为 第 2 种 序 列 要 更 长 一 些 ,处 理 器 必 须 花 更 多 的 时 间 来 访问 和 解 码 代 码 的 多 余 字 节 。 这 是 一 个 比 较 少 见 的 问 题 , 而 且 , 如 果 序 列 出 现 在 循环 内 部 ,因 为 循 环 的 第 一 次 重 复 之 后 ,处 理 器 将 从 它 的 指 令 缓 存 内 而 不 是 内 存 内取 出 代 码 。
当 试 图 决 定 两 个 序 列 中 谁 代 表 速 度 较 快 的 代 码 时 ,要 考 虑 到 另 一 个 能 进 一 步 影 响结 果 的 因 素 。第 2 个 序 列 比 第 1 个 序 列 要 使 用 更 多 的 寄 存 器 ,而 寄 存 器 有 利 于 帮
助 优 化 另 一 部 分 代 码 。估 计 另 一 种 优 化 方 法 的 整 体 效 果 经 常 是 相 当 困 难 的 。一 般来 说 , 当 对 大 小 而 不 是 速 度 作 出 假 设 时 , 可 能 要 容 易 猜 测 些 。
代 码 生 成 分 类
在 Category 框 中 选 择 Code Generatio n( 代 码 生 成 ),可 选 择 在 图 12-3 中 所 示 的 选项 , 包 括 :
-
优 化 处 理 器 类 型
-
编 译 器 假 设 的 默 认 调 用 约 定
-
应 用 程 序 所 采 用 的 运 行 库 类 型
-
结 构 成 员 的 对 齐
图 12-3 Code Generation 分 类 中 的 选 项
处 理 器
在 Processo r( 处 理 器 )框 中 ,可 以 选 择 优 化 的 处 理 器 级 别 。默 认 设 置 为 Blen d( 混合 ),它代表了一种折衷方案。在 Visual C++ 版 本 4 中 , Blend 设 置 使 得 编 译 器 主要 用 于 Intel 80486 的 优 化 ,为 80386 和 奔 腾 处 理 器 添 加 在 80486 上 并 不 禁 止 执 行的 优 化 。 在 版 本 5 和 版 本 6 中 , Blend 设 置 主 要 针 对 奔 腾 机 , 为 低 级 的 80486 处理 器 添 加 所 选 择 的 优 化 。
不 考 虑 处 理 器 设 置 ,编 译 器 所 产 生 的 仅 仅 是 能 被 80386 识 别 的 机 器 指 令 。这 能 保证 优 化 的 程 序 即 使 是 利 用 Pentium 或 Pentium Pro 设 置 来 编 译 , 也 能 运 行 在 低 级处 理 器 上 。 实 际 上 , Pentium 和 Blend 设 置 具 有 同 样 的 作 用 。
调 用 约 定
Calling Conventio n( 调 用 约 定 ) 框 中 的 选 择 确 定 为 项 目 或 所 选 定 源 文 件 的 默 认 调用 约 定 。此 设 置 仅 仅 指 定 了 默 认 调 用 约 定 , 所 有 约 定 都 明 显 地 包 括 在 超 越 默 认 设置 的 函 数 原 型 中 。调 用 约 定 为 调 用 程 序 和 被 调 用 的 函 数 确 立 了 规 则 ,指 定 参 数 以什 么 顺 序 压 入 堆 栈 中 , 外 部 名 字 怎 样 给 定 , 当 函 数 返 回 时 谁 将 清 除 堆 栈 。
Visual C++ 能 识 别 _cdecl, _fastcall 和 _stdcall 调 用 约 定 , 这 些 均 为 C 关 键 字 命 名 , 以 函 数 声 明 的 顺 序 指 定 约 定 , 约 定 总 结 如 下 :
序
左
_stdcall 从 右 至 左
@function@nnn
调 用 函 数
_function@nnn
表 中 最 右 端 栏 目 所 描 述 的 是 函 数 名 怎 样 出 现 在 目 标 列 表 中 , 其 中 , function 出 现在 源 程 序 中 时 , 它 代 表 函 数 名 , nnn 表 示 参 数 列 表 的 大 小 , 以 字 节 为 单 位 。 表 中所 总 结 的 方 案 仅 仅 应 用 在 C 程 序 , 以 及 extern "C "关 键 字 的 C++ 函 数 中 。 如 果 没有 关 键 字 , C++ 便 使 用 一 种 不 同 的 设 定 系 统 。
_cdecl 设 置 指 定 了 C 调 用 约 定 ,这 种 约 定 变 量 参 数 列 表 , 原 因 是 调 用 程 序 负 有 在函 数 返 回 后 清 除 堆 栈 的 义 务 。在 一 个 函 数 调 用 后 ,清 除 堆 栈 只 需 要 使 用 一 个 简 单的 机 器 指 令 , 以 便 来 重 新 设 置 堆 栈 指 针 。 代 码 并 不 多 , 尤 其 是 如 果 函 数 采 用 一 个参 数 的 情 况 下 , 此 时 , 一 字 节 的 POP 指 令 便 可 以 重 新 设 置 堆 栈 指 针 。 但 是 , 当采 用 更 多 的 函 数 调 用 时 , 清 除 堆 栈 的 指 令 便 会 对 程 序 少 许 增 加 一 些 开 销 。
_fastcall 约 定 改 进 了 调 用 至 少 占 用 一 个 参 数 的 C 函 数 的 速 度 。 在 这 种 约 定 中 , 前两 个 适 当 的 函 数 参 数 值 被 传 递 给 处 理 器 寄 存 器 。 所 有 其 他 参 数 传 递 到 函 数 中 ,方法 是 的 它 们 以 从 左 到 右 的 顺 序 压 入 堆 栈 。 在 Intel 系 统 中 , Visual C++ 利用 ECX 和 EDX 寄 存 器 为 _fastcall 函 数 传 递 参 数 。尽管 ECX 和 ECX 寄 存 器 自 从 C 7 时 代以 来 已 经 被 _fastcall 所 应 用 , 但 微 软 公 司 不 能 保 证 将 来 的 Visual C++ 发 行 版 本 将会 继 续 使 用 同 一 寄 存 器 ( 这 种 警 告 仅 适 用 于 含 有 内 嵌 汇 编 代 码 的 _fastcall 函 数 )。除 了 提 高 运 行 速 度 外 , _fastcall 约 定 通 常 会 稍 微 减 少 代 码 大 小 , 约 定 的 唯 一 不 利之 处 就 是 , 它 不 允 许 变 量 参 数 列 表 。
_stdcall 约 定 通 过 W indows API 来 调 用 约 定 , 当 应 用 给 有 固 定 参 数 列 表 的 函 数时 , _stdcall 类 似 于 _fastcall 约 定 , 只 是 它 不 能 向 寄 存 器 中 传 递 参 数 。 这 个 约 定 有利 于 减 少 程 序 代 码 大 小 , 因 为 堆 栈 清 除 的 义 务 属 于 被 调 用 的 函 数 , 而 不 是 调 用 程序 。 在 _fastcall 和 _stdcall 下 , 函 数 通 过 RET ( 返 回 ) 指 令 可 以 有 效 地 清 除 堆 栈 , 而 无 需 显 式 地 调 节 ESP 堆 栈 指 针 。 _stdcall 约 定 也 允 许 函 数 的 变 量 参 数 列 表 , 在这 种 情 况 下 , 调 用 以 与 _cdecl 一 样 的 方 式 实 现 , 强 迫 调 用 程 序 清 除 堆 栈 。
运 行 库
选 择 正 确 的 运 行 库 ,能 减 少 应 用 程 序 的 代 码 大 小 ,尽 管 通 常 没 有 必 要 去 调 整 默 认设 置 。 不 要 误 解 Multithreaded DLL 和 Debug Multithreaded DLL 的 设 置 , 设 置 中的 “ DLL ” 指 的 是 运 行 库 , 而 不 是 项 目 , 并 且 , 它 并 不 意 味 着 运 行 时 仅 仅 应 用 于创 建 动 态 链 接 库 的 项 目 。 Multithreaded DLL 设 置 将 项 目 与 用 于 Msvcrt.dll 的 导 入
库 链 接 起 来 , 这 是 一 个 可 重 新 分 布 的 动 态 链 接 库 , 它 含 有 C 运 行 库 的 版 本 , 而且 可 以 处 理 线 程 。 动 态 或 静 态 地 与 运 行 库 链 接 , 与 动 态 或 静 态 地 与 MFC 库 链 接所 需 考 虑 的 问 题 相 同 。 静 态 链 接 使 可 执 行 文 件 变 得 更 大 。 动 态 链 接 使 得 代 码 更小 , 但 是 , 要 求 用 已 完 成 的 应 用 程 序 来 重 新 分 配 Msvcrt.dll 。
在 表 12-4 中 ,总 结 了 Code Generation 分 类 中 用 来 指 定 项 目 运 行 库 的 Use Run-Time Library ( 使 用 运 行 库 ) 框 中 的 设 置 :
表 12-4 Use Run-Time Library 框 中 的 设 置 , 它 确 定 程 序 如 何 与 C 运 行 库 链 接设 置 运 行 库 说 明
Single-Threaded ( 单 线 程 ) M ultithreaded ( 多 线 程 ) M ultithreaded DLL
Debug Singlee-Threaded ( 单 线程 调 试 )
Debug Multithread ( 多 线 程 调试 )
Debug Multithreaded DLL( 调试 多 线 程 DLL)
Libc.lib
Libcmt.li b
Msvcrtd.l ib
Libcd.lib
Libcmtd.l ib
Msvcrtd.l ib
与 库 的 静 态 链 接 , 单 线程
与 库 的 静 态 链 接 , 单 线程
用 于 Msvcrtd.lib 的 导 入库
静 态 链 接 , 单 线 程 ( 调试 版 本 〕
静 态 链 接 , 多 线 程 ( 调试 版 本 〕
用 于 Msvcrtd.lib 的 导 入库
结 构 对 齐
在 Code Generation 分 类 中 , 最 后 的 设 置 指 定 了 结 构 和 联 合 成 元 对 齐 的 边 界 。 在结 构 的 第 1 个 成 员 后 , 随 后 的 每 个 成 员 落 在 内 存 边 界 上 , 这 个 边 界 是 按 成 员 的 大小 或 按 对 齐 的 设 置 来 决 定 的 , 取 其 中 的 最 小 值 。 设 定 结 构 对 齐 值 为 1 , 可 以 保 证在 结 构 成 员 之 间 没 有 浪 费 的 内 存 , 这 就 是 著 名 的 压 缩 结 构 的 技 术 。
压 缩 能 减 少 自 动 内 存 类 结 构 的 堆 栈 使 用 , 或 减 少 静 态 存 储 类 的 结 构 的 程 序 大 小 。对 压 缩 来 说 ,要 起 作 用 ,一 个 结 构 就 必 须 有 至 少 一 个 元 素 长 度 仅 为 1 或 2 个 字 节 , 而 且 放 置 在 一 个 更 大 的 多 字 节 成 员 之 前 , 如 一 个 整 数 。 例 如 , 考 虑 压 缩 对 这 个 简单 结 构 的 影 响 。
struct s
{
char ch; //One-byte element int I; //Four-byte element
}
对 齐 值 为 4 或 更 多 , 将 会 在 两 个 成 员 之 间 浪 费 3 个 字 节 , 原 因 是 编 译 器 将 成 员 i
置 于 一 个 双 字 边 界 上 。 然 而 , 对 齐 值 1 会将 i 压 缩 ch 邻 近 内 存 的 位 置 :
尽 管 压 缩 能 够 减 少 结 构 的 大 小 , 这 种 节 省 可 能 并 不 会 使 程 序 数 据 区 域 全 面 减 少 。它 取 决 于 结 构 里 的 成 员 的 混 合 状 况 , 以 及 内 存 中 结 构 的 数 据 目 标 。 如 果 一 个 整 数在 结 构 之 后 出 现 在 内 存 之 中 , 例 如 , 编 译 器 在 s.i 之 后 , 将 整 数 与 下 一 个 双 字 边界 对 齐 , 因 此 , 浪 费 的 字 节 通 过 压 缩 结 构 便 可 以 节 省 下 来 了 。
结 构 压 缩 需 要 运 行 速 度 方 面 的 代 价 ,因 为 处 理 器 当 从 内 存 中 读 取 不 对 齐 的 数 据 时
便 会 有 时 间 上 的 拖 延 。在 Intel 80486 和 奔 腾 处 理 器 上 ,均 能 够 在 一 个 内 存 引 用 周期 里 取 回 一 个 4 字 节 的 整 数 ,整 数 便 安 排 在 双 字 对 齐 边 界 上 。如 果 整 数 的 位 置 与它 的 优 化 边 界 有 偏 移 , 处 理 器 在 取 数 据 时 就 必 须 等 待 3 个 附 加 的 周 期 。 如 前 所述 , 在 存 储 整 数 s.i 时 , 对 齐 设 置 1 或 2 可 以 节 省 空 间 , 但 是 , 代 价 是 当 读 写 入整 数 时 , 访 问 时 间 要 增 加 4 倍 。
对 动 态 链 接 库 和 组 件 软 件 , 诸 如 ActiveX 控 件 , 结 构 压 缩 也 会 导 致 出 现 一 些 小 问题 。 如 果 库 导 出 一 个 函 数 , 这 个 函 数 采 用 结 构 作 为 参 数 , 或 者 返 回 结 构 指 针 , 调用 应 用 程 序 和 导 出 函 数 两 者 都 必 须 对 齐 结 构 。 看 一 下 前 面 的 图 , 将 会 使 你 确 信 , 当 调 用 应 用 程 序 使 用 4 或 更 高 的 对 齐 设 置 来 编 译 ,试 图 将 结 构 s 传 递 给 已 经 用 对齐 设 置 1 或 2 编 译 的 动 态 链 接 库 时 , 便 会 产 生 问 题 。 当 发 生 这 种 情 况 时 , 调 用 程序 和 库 对 整 数 s.i 找 到 的 内 存 位 置 是 不 同 的 。
如 果 你 的 库 将 结 构 或 指 针 传 递 给 一 个 结 构 , 应 当 更 多 地 考 虑 压 缩 所 带 来 的 后 果 。Visual Basic 假 定 结 构 对 齐 在 W O R D 边 界 上 , 因 此 , 为 了 成 功 地 共 享 一 个 结 构 , 对 齐 设 置 2 对 库 和 Visual Basic 调 用 程 序 来 说 是 必 须 的 。 当 在 一 个 头 文 件 中 声 明此 结 构 时 , 也 必 须 考 虑 使 用 pack 关 键 字 。 接 着 , 使 头 文 件 对 于 调 用 你 的 库 来 编写 C/C++ 应 用 程 序 的 开 发 人 员 都 可 用 。用 相 同 的 头 文 件 编 译 库 和 调 用 程 序 来 确 保结 构 对 齐 的 一 致 , 而 不 用 管 在 Project Settings 对 话 框 中 的 选 项 。
#pragma pack( push, PACK_S ) // Save current alignment setting #pragma pack( 2) //Word-align the structure
struct s
{
char ch; int I;
};
#pragma pack( pop, PACK_S ) //Restore original alignment
自 定 义 分 类
Customize 分类 ( 图 12-4 中 ) 控 制 启 用 函 数 级 别 的 链 接 和 重 复 字 符 串 的 消 除( 字符 串 共 用 ) 的 优 化 。 两 种 优 化 都 是 Maximize Speed( 最 大 速 度 ) 选 项 中 的 一 个 整体 部 分 。如 果 在 General 分 类 中 设 置 Maximize Speed ,标 有 Enable Function_Level Linking ( 启 用 函 数 级 链 接 ) 和 Eliminate Duplicate Strings( 消 除 重 复 字 符 串 ) 的复 选 框 在 Customize 分 类 中 便 被 禁 用 。 这 似 乎 也 指 出 了 那 些 优 化 同 时 也 失 去 作用 , 但 并 不 是 这 样 , 选 择 Maximize Speed 将 打 开 两 种 优 化 。 为 了 使 复 选 框 发 生作 用 , 首 先 在 General 分 类 中 选 择 M inimize Size 或 Customize , 如 前 面 所 述 。
图 12-4 Customize 分 类 中 的 选 项
函 数 级 别 的 链 接 仅 仅 应 用 于 封 装 的 函 数 。也 就 是 说 , 函 数 通 过 目 标 列 表 中 的 一 个COMDAT 记 录 向 链 接 器 进 行 标 识 。 在 C/C++ 类 声 明 中 的 内 嵌 成 员 函 数 是 自 动 封装 的 , 而 别 的 成 员 函 数 不 是 这 样 。 为 了 编 译 所 有 封 装 形 式 的 函 数 , Enable Function-Level Linking ( 启 用 函 数 级 链 接 ) 复 选 框 必 须 打 开 ( 或 者 , 如 果 选 择
M aximize Speed 设 置 , 则 仍 禁 用 )。
优 化 分 类
Optimizations( 优 化 ) 分 类 为 用 于 项 目 中 的 优 化 类 型 提 供 更 细 微 的 控 制 , 并 且 允 许你 指 定 编 译 器 是 否 应 该 扩 大 函 数 内 嵌 。 此 分 类 显 示 General 分 类 中 所 选 择 的 同 样的 优 化 设 定 , 这 些 设 置 必 须 进 行 自 定 义 , 来 启 用 图 12-5 中 所 示 的 复 选 框 , 这 将允 许 你 从 编 译 器 优 化 列 表 中 选 择 。 Customize 设 置 为 打 开 Assume No Aliasing 优化 提 供 了 唯 一 的 方 法 。
图 12-5 Optimizations 分 类 中 的 选 项
在 此 列 表 中 , 最 后 的 复 选 框 开 关 标 有 Full Optimizatio n( 完 全 优 化 ), 它 打 开 一 系
列 优 化 , 包 括 内 嵌 扩 充 、 内 在 函 数 、 支 持 快 速 代 码 、 无 堆 栈 检 查 以 及 全 局 优 化 。在 此 列 表 中 , 可 能 只 需 要 解 释 标 为 Improve Float Consistency ( 改 进 浮 点 一 致 性 ) 的 复 选 框 。这 个 开 关 当 被 关 闭 时 实 际 上 是 一 种 优 化 。它 的 代 价 是 需 要 更 多 的 代 码和 更 慢 的 浮 点 操 作 ,打 开 开 关 , 会 使 编 译 器 采 取 下 列 步 骤 来 减 少 浮 点 舍 入 错 误 的机 会 。
-
在 每 个 浮 点 操 作 之 前 添 加 指 令 , 从 内 存 中 复 制 数 据 到
浮 点 寄 存 器 上 。尽 管 这 将 会 在 很 大 程 度 上 减 慢 操 作 , 但 是 计 算 结 果 可 以 保 证 得 到 数 据类 型 所 能 容 纳 的 精 确 数 据 。
-
禁 用 执 行 浮 点 运 算 的 运 行 函 数 内 嵌 的 内 在 形 式 , 在 表
12-3 中 已 列 出 , 程 序 使 用 标 准 的 运 行 函 数 。
-
禁 用 这 样 的 计 算 优 化 , 它 允 许 计 算 结 果 为 浮 点 处 理 器
的 80 位 精 度 。
这 些 步 骤 把 浮 点 计 算 的 结 果 维 持 在 32 位 或 64 位 精 度 ,有 利 于 确 保 两 个 浮 点 数 字能 更 准 确 地 测 试 其 是 否 相 等 。 而 且 , 即 使 打 开 了 Improve Float Consistenc y( 改 进浮 点 一 致 性 ) 开 关 , 它 仍 然 是 一 个 好 主 意 , 在 比 较 float 型 和 double 型 数 字 时 , 可 以 容 许 少 量 的 偏 差 , 例 如 :
#define TOLERANCE 0.00001
double x = 2.0, y = sqrt(4.0 );
if (x + TOLERANCE > Y && x - TOLERANCE <y>
{
// x and y are equal
}
在 Optimizations 分 类 组 合 框 中 ,提 供 了 一 定 数 量 的 控 制 ,它 们 针 对 编 译 器 怎 样 利用 等 价 的 内 嵌 代 码 来 代 替 函 数 调 用 , 三 种 选 择 如 下 :
- Only_inlin e( 只 有 内 嵌 的 ):仅 仅 替 换 标 有
_inline,inline 或 _forceinline 关 键 字 的 目 标 函 数 , 或 者 对 于 类 声 明 中 定 义 的 类 成 员 函 数 。 当 优 化 速度 时 , 编 译 器 使 用 内 嵌 代 码 替 代 所 有 这 样 的 函 数 调 用 。 当 Visual C++
优 化 尺 寸 时 , 就 不 完 全 相 同 了 。 如 果 Favor Small Code 选 项 已 经 起 作用 , 编 译 器 不 会 扩 大 标 有 _inline 或 inline 的 太 大 函 数 。 甚 至 当 内 嵌 大范 围 使 用 时 , 这 也 会 保 证 得 到 更 好 的 优 化 结 果 。 新 的 _forceinline 关 键字 超 出 了 编 译 器 的 能 力 , 尽 管 不 是 在 每 种 情 况 下 均 如 此 。 例 如 , 强 制采 用 变 量 参 数 列 表 的 函 数 的 内 嵌 , 或 者 是 不 能 被 inline recursion 关 键字 识 别 的 递 归 函 数 。
-
Any suitable ( 任 何 适 用 的 ): 除 了 被 Only_inline 设 置
覆 盖 的 函 数 外 , 这 种 选 项 也 能 替 代 函 数 的 调 用 , 那 些 函 数 是 编 译 器 认 为 太 小 , 不 足 以进 行 内 嵌 的 函 数 。 微 软 公 司 没 有 为 这 样 的 函 数 说 明 编 译 器 标 准 。
-
Disabl e( 禁 用 ): 没 有 设 置 内 嵌 , 即 使 对 调 用 标 有
_inline 或 _forceinline
的 函 数 。
从 调 试 到 发 行
在 产 品 的 生 产 周 期 中 , 需 要 不 时 地 建 立 应 用 程 序 的 发 行 版 本 。第 一 次 发 行 版 本 的建 立 可 能 要 等 到 编 完 调 试 版 本 以 后 的 数 周 或 数 月 后 。一 般 来 说 , 创 建 发 行 目 标 仅仅 涉 及 到 一 个 新 的 建 立 版 本 ,但 偶 尔 也 存 在 其 他 一 些 问 题 。这 节 主 要 讨 论 当 从 调试 转 到 发 行 时 , 可 能 产 生 的 潜 在 缺 陷 , 并 解 释 如 何 避 开 它 们 。
为 了 建 立 程 序 的 发 行 版 本 ,在 Build 菜 单 中 单 击 Set Active Configuratio n( 设 置 活动 配 置 ) 命 令 , 并 选 择 Win32 Release , 或 者 在 Build 工 具 栏 中 选 择 目 标 。
在 Project Settings 对 话 框 中 设 置 所 希 望 的 优 化 开 关 , 然 后 单 击 Build 命 令 。 你 可以 注 意 到 , 编 译 程 序 的 发 行 版 本 所 花 的 时 间 , 要 比 编 译 调 试 版 本 要 多 , 这 是 因 为编 译 器 优 化 时 需 做 更 多 的 工 作 。
对 一 个 应 用 程 序 来 说 , 如 果 它 在 调 试 状 态 正 确 运 行 , 在 作 为 发 行 目 标 重 新 编 译 , 根 据 优 化 器 进 行 中 间 过 程 的 处 理 时 , 很 少 会 出 问 题 。 仅 仅 在 别 名 的 情 况 下 , 才 可能 出 错 , 通 常 , 这 是 很 少 见 的 。 隐 藏 的 别 名 可 以 在 程 序 员 陌 生 的 代 码 中 。 如 果 一个 调 试 应 用 程 序 在 打 开 Assume No Aliasing 选 项 时 失 败 , 则 问 题 出 自 所 存 在 的 隐含 别 名 。 通 过 重 新 建 立 发 行 版 本 , 而 且 关 闭 Assume No Aliasing , 可 以 很 容 易 地检 测 到 这 个 条 件 。 如 果 应 用 程 序 仍 然 失 败 , 就 不 应 当 责 怪 优 化 器 。 当 应 用 程 序 从调 试 版 本 移 到 发 行 版 本 时 , 为 何 一 个 应 用 程 序 可 能 中 断 , 原 因 有 许 多 。
例 如 , ASSERTs 在 发 行 模 式 中 , 编 译 器 忽 略 在 ASSERT 宏 中 的 代 码 。 如 果 断 言的 代 码 调 用 一 个 函 数 , 或 执 行 一 些 在 ASSERT 之 外 的 代 码 所 要 求 的 其 他 任 务 , 这 将 会 引 起 一 些 问 题 , 考 虑 如 下 例 子 :
ASSERT ((ptr=GetPointer())!=NULL);
X=*PTR;
在 调 试 时 , 这 种 代 码 能 够 正 确 运 行 。 在 发 行 版 本 里 , 代 码 可 能 会 引 起 错 误 , 因 为ptr 没 有 被 初 始 化 。 解 决 方 法 就 是 在 ASSERT 之 前 调 用 GetPointer 函 数 , 或 使 用VERIFY 宏 代 替 ASSERT 。 对 于 使 用 # ifdef_DEBUG 加 前 缀 的 条 件 代 码 , 也 可 能会 发 生 类 似 的 问 题 。因 为 编 译 器 不 会 预 先 在 发 行 模 式 中 定 义 _DEBUG , 条 件 块 中的 代 码 不 必 执 行 影 响 块 外 代 码 的 行 为 。尽 管 这 看 上 去 很 明 显 ,但 许 多 程 序 员 经 常犯 简 单 的 错 误 。
禁 用 堆 栈 检 查 也 能 引 起 一 些 问 题 , 对 一 个 函 数 来 说 , 它 可 能 需 要 更 多 的 堆 栈 空 间页 , 用 于 其 局 部 变 量 。 尽 管 在 程 序 的 调 试 版 本 里 , 函 数 可 以 通 过 第 一 次 调 用 一 个堆 栈 检 查 例 程 来 成 功 地 运 行 , 当 优 化 时 , 如 果 禁 用 堆 栈 检 查 , 则 可 能 会 失 败 。 解决 办 法 就 是 重 写 函 数 , 以 到 达 随 后 页 面 的 堆 栈 内 存 , 或 者 ,插入一个 check_stack 程 序 , 选 择 性 地 使 堆 栈 检 查 对 函 数 起 作 用 。
在 调 试 和 发 行 模 式 之 间 , 编 译 器 行 为 可 以 用 其 他 更 微 妙 的 方 法 来 改 变 。 例 如 , 在调 试 版 本 里 , new 运 算 符 对 内 存 分 配 添 加 保 护 字 节 。 无 意 之 中 依 赖 于 这 些 额 外 字节 的 程 序 也 许 在 其 发 行 版 本 中 会 运 行 失 败 。
函 数 参 数 可 以 用 任 何 次 序 来 求 值 , 而 且 , 必 须 保 证 参 数 顺 序 在 程 序 的 调 试 版 本 和发 行 版 本 中 保 持 一 致 。接 下 来 的 例 子 在 一 种 版 本 中 可 以 正 确 运 行 , 而 在 另 一 种 版本 中 不 能 :
Function1( ptr=Getpointer(),*ptr);
隐 藏 的 线 程 问 题 有 时 仅 在 程 序 的 发 行 版 本 中 出 现 ,如 常 见 的 错 误 有 ,两 个 线 程 同时 访 问 一 个 写 静 态 变 量 的 函 数 。 在 调 试 版 本 里 , 变 量 总 是 保 持 在 内 存 里 , 因 此 , 线 程 间 的 潜 在 冲 突 不 会 由 于 时 间 上 的 轻 微 差 别 而 出 现 。在 发 行 版 本 里 ,错 误 出 现的 机 会 会 更 多 , 因 为 变 量 能 为 函 数 运 行 的 持 续 时 间 而 重 新 寄 存 , 这 将 使 得 一 个 线程 覆 盖 其 他 结 果 更 为 可 能 。
- 个 优 化 过 的 程 序 可 能 会 运 行 失 败 , 原 因 可 能 是 许 多 其
他 类 型 的 源 代 码 问 题 ,它们 中 的 一 些 如 表 12-5 所 示 。 为 了 跟 踪 某 一 问 题 , 应 当 尽 量 单 独 打 开 这 些 优 化 , 以 确 定 错 误 在 什 么 情 形 下 会 出 现 。表 中 的 第 2 栏 提 供 了 一 些 建 议 , 在 检 查 代 码 时应 该 查 询 这 些 。
表 12-5 从 代 码 优 化 中 产 生 的 典 型 源 代 码 问 题优 化 可 能 引 起 的 问 题
内 嵌 扩 展全 局 优 化
产 生 固 有 的 函 数 内 嵌改 善 浮 点 一 致 性
结 构 指 针 的 忽 略
未 初 始 化 的 局 部 变 量未 初 始 化 的 局 部 变 量未 初 始 化 的 局 部 变 量
比 较 时 依 赖 于 确 切 的 精 度
由 于 不 正 确 的 函 数 原 型 而 引 起 堆栈 破 坏
在 发 行 版 本 出 问 题 时 怀 疑 优 化 器 , 这 可 能 是 人 类 的 本 性 。 毕 竟 , 编 译 器 用 我 们 不熟 悉 的 方 法 来 重 写 代 码 , 但 是 , 在 Visual C++ 编 译 器 里 , 代 码 优 化 技 术 的 可 靠 性相 当 高 。 微 软 公 司 在 自 己 的 主 要 产 品 里 , 例 如 W indows 95 , W indows NT 和M icrosoft Office , 使 用 C/C++ 语 言 编 写 它 们 , 并 进 行 优 化 , 这 体 现 了 微 软 公 司 具有 足 够 的 自 信 。 单 凭 这 个 事 实 , 就 应 该 减 轻 人 们 对 优 化 不 安 全 的 担 心 。
基 准 Visual C++
当 Visual C++ 4.0 版 本 处 在 β 测 试 阶 段 时 , 微 软 公 司 请 我 去 执 行 新 产 品 的 基 准 测试 , 并 写 一 篇 讨 论 基 准 结 果 和 方 法 的 文 章 。 此 测 试 将 Visual C++ 与 三 种 合 格 的 产品 相 比 较 , 以 查 看 对 于 相 同 的 源 代 码 ,哪 种 编 译 器 将 产 生 最 快 的 或 最 小 的 可 执 行代 码 。得 到 的 结 果 将 是 每 个 编 译 器 能 力 的 可 靠 性 量 度 , 由 此 可 以 知 道 如 何 最 好 地优 化 源 代 码 。
Visual C++ 在 基 准 测 试 中 做 的 较 好 , 可 以 说 是 相 当 不 错 , 实 际 上 , 这 并 不 是 本 节的 要 点 。 然 而 , 它 可 以 使 我 们 得 知 , 要 查 看 基 准 测 试 代 码 中 的 单 个 函 数 , 将 会 涉及 到 复 杂 的 数 字 计 算 。 这 种 特 殊 的 函 数 , 代 表 了 在 基 准 测 试 中 , 结 果 将 是 各 不 相同 的 , 它 生 动 地 演 示 了 一 些 聪 明 的 编 译 器 优 化 可 以 获 得 的 潜 在 好 处 。 尽 管 所 有 4 个 编 译 器 都 进 行 设 置 ,来 为 函 数 的 最 大 速 度 进 行 优 化 ,在 此 部 分 的 测 试 中 ,Visual C++ 产 生 的 可 执 行 程 序 的 运 行 速 度 要 比 占 第 2 位 的 代 码 速 度 快 3 倍 多 , 从 反 汇 编代 码 中 可 以 得 知 原 因 何 在 :
Microsoft Visual C++ 4 最 接 近 的 竞 争 对 手
?Fnx9@@YAXXZ PROC NEAR ?Fnx9@@YAXXZ:
push esi push EBX
push edi push ESI
$L1220: mov dwod ptr -038h[EBP], 5
mov ebx, DWORD PTR ?X@@3PAVTest@@A [edx] mov dword ptr -034h[EBP], 8
续 表
mov ebi, DWORD PTR ?X@@3PAVTest@@A [edx+4] mov ESI,offset FLAT:?Y@@3QAVTest @@A
mov EBX,offsetFLAT:? X@@3QAVTest @@A
mov EDX, -034h[EBP]
mov EAX, -038h[EBP]
lea ecx, DWORD PTR [edi*8] mov -020h[EBP],EAX
sub eax, ecx mov -01ch[EBP],EDX
mov ecx,DWORD PTR ?Y@@3PAVTest@@A [edx-4] L352:
add eax, DWORD PTR [esi] mov EDX, 4[ESI]
mov EAX, [ESI]
mov -010h[EBP],EAX
续 表
lea mov |
ebx,DWORD PTR [edi+edi*4] edi, DWORD PTR $T1455[esp+20] |
mov mov |
-0Ch[EBP],EDX EDX,4[EBX] |
---|---|---|---|
add |
eax, ebx |
mov |
EDX, [EBX] |
mov |
DWORD PTR [esi], edi |
mov |
-018h[EBP],EAX |
mov |
DWORD PTR [esi], edi |
mov |
-018h[EBP],EAX |
mov |
DWORD PTR [esi+4], eax |
mov |
-014h[EBP],EDX |
cmp |
edx, 8000 |
mov |
EDX,-020h[EBP] |
jl |
SHORT $l1220 |
imul |
EDX, -018h[EBP] |
pop |
edi |
mov |
EDX, -01Ch[EBP] |
pop |
esi |
imul |
EDX, -014h[EBP] |
pop |
ebx |
sub |
续 表 ECX,EDX |
|
---|---|---|---|---|
add |
esp, 8 |
|
mov |
-030h[EBP], ECX |
ret |
0 |
mov imul |
ECX, -020h[EBP] ECX, -014h[EBP] |
|
mov imul |
ECX, -01Ch[EBP] ECX, -018h[EBP] |
|||
add |
ECX,EDX |
|||
mov |
-02Ch[EBP],ECX |
|||
mov |
EDX, -02Ch[EBP] |
|||
mov |
EAX, -030h[EBP] |
续 表
mov |
-8[EBP], EAX |
---|---|
mov |
-4[EBP],EDX |
mov |
ECX,-010h[EBP] |
add |
ECX,-8[EBP] |
mov |
-028h[EBP],ECX |
mov |
ECX,-0Ch[EBP] |
add |
ECX,-4[EBP] |
mov |
-024h[EBP],ECX |
mov |
EDX,-024h[EBP] |
mov |
EAX,-028h[EBP] |
续 表
mov |
[ESI],EAX |
---|---|
mov |
4[ESI],EDX |
mov |
ECX, 8 |
add |
ESI, ECX |
add |
EBX, ECX |
cmp |
EBX,offset FLAT:?@@3QAVTest@@A |
jb |
L352 |
pop |
ESI |
pop |
EBX |
mov |
ESP, EBP |
续 表
ret
除 了 在 大 小 上 的 明 显 不 同 外 , 在 序 言 和 结 尾 部 分 中 , 标 有 和 的 两 个 列 表 之 间的 差 异 也 是 显 而 易 见 的 。 通 过 结 构 指 针 的 忽 略 进 行 优 化 时 , Visual C++ 产 生 的 代码 分 配 8 字 节 堆 栈 , 并 且 , 通 过 使 用 ESP 寄 存 器 的 偏 移 量 来 访 问 函 数 的 堆 栈 结构 。 其 他 的 编 译 器 , 不 能 提 供 堆 栈 指 针 忽 略 作 为 优 化 技 术 , 必 须 压 入 和 弹 出 EBP 寄 存 器 , 来 使 它 更 适 于 用 作 结 构 指 针 。 而 且 , 再 进 一 步 看 , 对 于 此 函 数 来 说 , 揭露 堆 栈 结 构 甚 至 是 没 有 必 要 的 , 而 且 , Visual C++ 失 去 了 优 化 在 列 表 中 标 记 的代 码 的 机 会 。 虽 然 EBP 寄 存 器 因 为 结 构 指 针 的 忽 略 现 在 是 空 闲 的 , 代 码 仍 然 使用 堆 栈 来 支 持 中 间 计 算 。这 将 会 浪 费 掉 结 构 指 针 忽 略 所 带 来 的 好 处 :为 了 在 其 他优 化 中 使 用 , 便 释 放 EBP 寄 存 器 。 如 果 写 成 如 下 方 式 , 标 有 的 序 列 将 会 稍 快 , 而 且 更 小 一 些 :
mov lea |
ebp, eax eax,DWORD PTR [ecx+ebx*8] |
;Store intermediate calculation ;With value temporarily saved, |
---|---|---|
lea |
ebx,DWORD PTR [edi+edi*4] |
; we can use EAX |
mov edi, ebp ;Recover calculation in EDI
利 用 EBP 来 存 储 中 间 计 算 结 果 , 在 每 次 循 环 中 , 将 可 以 节 省 两 次 内 存 访 问 。
Visual C++ 版 本 可 以 节 省 代 码 空 间 , 并 通 过 组 合 地 址 模 式 为 一 条 指 令 , 来 赢 得 速度 。 这 允 许 它 为 简 单 的 算 术 计 算 使 用 LEA ( 加 载 有 效 地 址 ) 指 令 , 这 是 Intel 处理 器 的 一 个 显 著 特 征 。 通 过 使 用 左 移 和 加 法 运 算 , LEA 指 令 能 操 纵 基 本 的 和 索引 的 寄 存 器 , 以 便 执 行 某 种 乘 法 操 作 , 这 比 处 理 器 的 乘 法 指 令 M U L 和 IMUL 指令 要 快 得 多 。例 如 ,这 里 有 一 个 LEA 指 令 ,它 能 够 乘 以 存 在 EAX 寄 存 器 里 的 值 :
指 令 |
说 明 |
|
---|---|---|
lea |
eax, [eax*2] |
EAX × 2 |
lea |
eax, [eax+eax*2] |
EAX × 3 |
lea |
eax, [eax*4] |
EAX × 4 |
lea |
eax, [eax+eax*4] |
EAX × 5 |
lea |
eax, [eax*8] |
EAX × 8 |
lea |
eax, [eax+eax*8] |
EAX × 9 |
尽 管 用 这 种 方 式 使 用 LEA 由 Intel 公 司 清 楚 地 编 制 了 文 档 , 其 他 编 译 器 恢 复 为 4 个 IM U L 指 令 ,即 使 它 们 在 一 个 重 复 了 一 千 次 的 循 环 中 发 生 ,其 代 价 是 相 当 昂 贵的 。 从 其 他 编 译 器 产 生 的 循 环 长 度 也 可 以 知 道 , 需 要 使 用 约 39 个 机 器 指 令 。 这个 Visual C++ 版 本 的 循 环 方 式 要 求 仅 仅 是 18 个 指 令 ,并 且 根 本 没 有 IM U L 指 令 。
其 他 编 译 器 不 太 在 意 其 使 用 的 寄 存 器 , 考 虑 到 从 标 有 的 代 码 里 取 出 的 序 列 , 在这 里 , 值 被 写 入 堆 栈 中 , 并 且 能 立 即 再 次 获 取 :
mov -02ch[EBP],ECX
mov EDX,-02CH[EBP]
因 为 ECX 寄 存 器 已 经 被 控 制 , 所 以 , 从 内 存 中 再 次 读 取 这 个 值 是 没 有 必 要 的 , 如 果 像 以 下 这 样 , 代 码 序 列 将 会 更 小 :
mov -02CH[EBP],ECX ;store the ECX value mov EDX,ECX ;Also copy it to EDX
两 个 编 译 器 似 乎 要 进 行 合 理 的 尝 试 , 以 避 免 流 水 线 通 过 正 确 的 指 令 顺 序 时 会 停顿 。 人 们 不 应 该 期 望 编 译 器 能 正 确 排 序 , 因 为 它 通 过 代 码 传 递 的 事 情 太 多 了 , 导致 建 立 时 间 长 得 无 法 让 人 接 受 。 在 搜 寻 优 化 指 令 顺 序 时 , Visual C++ 编 译 器 仅 仅错 误 了 一 次 , 产 生 了 标 有 的 3 个 指 令 序 列 :
lea |
esi, |
DWORD |
PTR ?Y@@3PAVTest@@A[edx] |
---|---|---|---|
add |
edx,8 |
lea eax,DWORD PTR [ebx+ebx*4]
第 2 条 指 令 不 能 改 变 EDX 寄 存 器 , 直 到 第 1 条 指 令 已 经 完 成 读 取 它 。 改 变 第 2
和 第 3 条 指 令 的 顺 序 , 可 以 避 免 潜 在 的 停 止 , 确 保 序 列 中 邻 近 的 指 令 能 同 时 运
行 :
lea esi, DWORD PTR ?Y@@3PAVTest@@A[edx] lea eax,DWORD PTR [ebx+ebx*4]
add edx,8
对 版 本 4 来 说 , 这 就 够 了 。 版 本 5 对 优 化 器 进 行 了 大 量 改 进 , 对 于 个 人 计 算 机 来说 ,Visual C++ 确 实 具 有 优 化 的 编 译 器 技 术 。目 前 的 发 行 版 本 更 进 一 步 进 行 改 进 , 但 是 注 意 力 不 在 新 技 术 上 , 而 是 更 多 地 放 在 了 调 节 编 译 器 的 能 力 上 ,以 便 为 使 代码 更 小 和 速 度 更 快 寻 求 机 会 。 微 软 公 司 估 计 , Visual C++ 在 版 本 4 中 , 能 够 减 少的 可 执 行 代 码 大 小 超 过 10 % , 并 且 , 再 次 编 译 本 节 中 讨 论 过 的 代 码 , 便 可 以 知道 , 它 的 改 进 确 实 很 显 著 。 代 码 大 小 缩 小 约 16% , 而 且 , 现 在 , 编 译 器 将 指 出 怎样 完 全 废 除 堆 栈 结 构 , 而 不 依 赖 于 序 言 代 码 和 结 束 代 码 :
?Fnx9@@YAXXZ PROC NEAR
push esi
push edi
xor eax, eax
$L1621:
mov edx, DWORD PTR ?X@@3PAVTest@@A[eax+4] mov ecx, DWORD PTR ?X@@3PAVTest@@A[eax] add eax, 8
lea edi, DWORD PTR [edx*8]
lea edx, DWORD PTR [edx+edx*4] lea esi, DWORD PTR [ecx+ecx*4]
lea ecx, DWORD PTR [edx+ecx*8]
cmp eax, 8000
jl SHORT $L1621
pop edi
pop esi
ret 0
标 有 代 码 的 程 序 段 使 我 们 可 以 更 清 楚 优 化 编 译 器 的 工 作 。 优 化 器 用 3 个 步 骤 写下 本 代 码 段 , 代 码 从 内 存 中 读 取 两 个 整 数 , 将 它 们 添 加 到 寄 存 器 的 值 中 , 然 后 , 将 它 们 的 和 写 回 到 内 存 中 , 用 下 面 3 个 指 令 来 替 代 所 有 标 有 的 代 码 :
sub esi, edi
add DWORD PTR ?Y@@3PAVTest@@A[eax-8],esi
add DWORD PTR ?Y@@3PAVTest@@A[eax-4],ecx
修 正 的 版 本 所 呈 现 的 是 , 读 取 操 作 是 没 有 必 要 的 ,并 且 进 一 步 减 少 函 数 的 大 小 约21 % , 尽 管 它 不 能 提 高 运 行 速 度 。 添 加 一 个 寄 存 器 的 值 到 内 存 中 , 比 复 制 这 个 值要 慢 得 多 , 但 是 , 其 补 偿 是 指 令 数 减 少 了 , 因 为 处 理 器 读 取 的 代 码 更 少 。 修 正 后的 函 数 虽 然 更 小 一 些 , 但 仍 会 同 以 往 的 Visual C++ 编 译 器 生 成 的 一 样 , 以 同 样 的速 度 来 运 行 。
如 果 你 在 自 己 的 程 序 里 对 研 究 编 译 器 优 化 的 影 响 有 兴 趣 , Visual C++ 可 以 产 生 汇编 语 言 列 表 , 像 在 本 节 中 所 列 出 的 那 些 一 样 。 在 Project Settings 对 话 框 的 C/C++ 选 项 卡 里 , 选 择 Listing Files ( 列 表 文 件 ) 分 类 , 然 后 , 选 择 在 Listing Files Type
( 列 表 文 件 类 型 ) 框 中 所 希 望 的 汇 编 列 表 类 型 。
第 13 章 自 定 义 Visual C++
随 着 对 Visual C++ 的 逐 渐 熟 悉 , 你 会 迫 切 希 望 改 变 它 的 某 些 特 性 , 以 便 更 好 地 配合 自 己 的 工 作 风 格 。 可 以 根 据 自 己 的 诸 多 喜 好 , 来 改 变 环 境 的 许 多 方 面 , 从 文 本编 辑 器 窗 口 的 内 容 , 到 自 定 义 工 具 栏 的 外 观 。 我 们 可 以 用 复 杂 的 宏 语 言 和 附 加库 , 来 扩 充 工 作 环 境 的 很 多 方 面 , 给 Visual C++ 编 制 新 的 功 能 , 而 这 些 功 能 即 便是 设 计 者 也 不 曾 想 到 。 本 章 阐 述 了 自 定 义 Visual C++ 诸 多 方 法 中 的 几 种 , 采 用 这些 方 法 , 能 获 得 更 富 效 率 的 工 作 环 境 。
大 多 数 环 境 行 为 都 具 有 记 忆 功 能 , 这 就 意 味 着 , 一 次 只 需 调 整 一 个 设 置 。 其 后 , 这 个 调 整 的 环 境 会 在 下 次 启 动 Visual c++ 时 变 为 默 认 的 环 境 。 例 如 , 退 出 Visual C++ 时 , 如 果 文 本 编 辑 器 窗 口 处 于 满 屏 , 则 在 下 次 运 行 该 程 序 时 , 将 再 次 自 动 出现 满 屏 方 式 。 某 些 自 定 义 的 设 置 不 很 明 显 , 如 果 不 知 从 何 处 查 看 , 将 很 难 找 到 它们 。
在 Tools( 工 具 ) 菜 单 中 , 有 Options 和 Customize 两 项 指 令 , 从 中 , 可 以 直 接 访问 给 管 理 Developer Studio 环 境 的 行 为 指 令 的 开 关 和 选 项 。 虽 然 不 能 从 这 些 命 令中 访 问 所 有 的 定 制 设 置 , 但 这 还 是 一 个 相 当 不 错 的 起 点 。
Option s( 选 项 ) 对 话 框
如 表 13-1 所 示 , Options 命 令 显 示 许 多 选 项 卡 , 这 个 Options 对 话 框 中 包 含 着 范围 广 泛 的 设 置 集 合 , 包 括 Visual C++ 编 辑 器 和 调 试 器 的 行 为 及 外 观 、 文 本 编 辑 器中 的 制 表 位 间 隔 和 缩 进 、编 译 和 链 接 期 间 中 使 用 的 文 件 和 库 的 目 录 位 置 、不 同 窗口 所 用 的 字 体 类 型 和 文 本 颜 色 。 表 13-1 列 举 了 一 些 对 话 框 的 选 项 。
表 13-1 单 击 Tools ( 工 具 ) 菜 单 中 的 Options , 在 调 出 的 Options 对 话 中 进 行设 置
Options ( 选 项 ) 对 话 框 中 的 选 项 卡 说 明
文
本 编 辑 器 ─ ─ 启 用 文 本编 辑 器 里 的 滚 动 条 和 选 择边 界
。选 择 影 响 编 译 器 如 何
保 存 文 件 的 设 置 ,以 及 在 文档 窗 口 里 是 否 自 动 出 现 语句 的 自 动 补 全 特 征
续 表
文 本 文 档 里 的 制 表 位 位 置
─ ─ 在 文 本 文 档 里 设 置 制
表 位 宽 度 ,以 及 源 代 码 的 自
动 缩 进 。确 定 编 辑 器 是 否 应当 接 受 制 表 位 作 为 ASCII 字 符 , 或 者 , 在 你 键 入 时 , 将 它 们 转 换 为 等 量 的 间 隔
续 表
调
式 器 设 置 ─ ─ 选 择 16 进制 或 10 进 制 显 示 , 以 及 调试 器 里
不 同 窗 口 的 外 型 。 指
定 显 示 在 M emory 窗 口 里 的默 认 地 址 , 启 用 即 时 调 试 , 并 在 Edit and Continue 特 征的 自 动 或 手 工 应 用 程 序 之间 选 择
续 表
文
本 编 辑 器 选 项 ─ ─ 选 择文 本 编 辑 器 仿 真
( Developter Studio, BRIEF, Epsilon ), 启 用 其 他 选 项 , 例 如 对 话 框 编 辑 器 里 双 击的 虚 拟 空 间 和 行 为
续 表
定
位 文 件 的 目 录 ─ ─ 选 择Executable files( 可 执 行 文件 ) 、
Include files ( 包 括 文
件 )、 Library files( 库 文 件 ) 或 Source files ( 源 文 件 ), 然 后 添 加 或 删 除 文 件 路径 , Visual C++ 按 列 出 的 顺序 扫 描 每 条 路 径 来 搜 索 文件
续 表
工
作 空 间 选 项 ─ ─ 启 用 或禁 用 窗 口 的 停 靠 , 设 置 Visual C++
启 动 时 大 部 分 最
近 项 目 中 自 动 加 载 的 内容 。 决 定 W indow ( 窗 口 )菜 单 是 否 按 字 母 顺 序 来 排
序 文 件 。通 过 在 Fil e( 文 件 ) 菜 单 中 的 Recent File s( 最 新文 件 ) 和 Recent Workspace
( 最 新 工 作 空 间 ) 命 令 , 来设 置 显 示 的 列 表 的 范 围
续
表
文 本 窗 口 里 的 颜 色 与 字 体
─ ─ 为 选 择 的 窗 口 设 定 字
体 类 型 和 大 小 。 设 定 各 种 文
本 成 员 的 颜 色 , 诸 如 注 释 、关 键 字 和 H T M L 标记
Options 对 话 框 中 的 Editor, Tabs 和 Compatibility 选 项 卡 仅 应 用 于 文 本 编 辑 。而 且 , 这 些 选 项 卡 中 的 大 多 数 设 置 同 时 影 响 编 辑 器 的 标 准 和 全 屏 幕 显 示 ,以 及 调 试 器 的源 文 本 窗 口 。 另 一 些 设 置 取 决 于 当 前 的 查 看 方 式 , 以 及 调 试 器 是 否 处 于 运 行 状态 , 这 就 为 在 环 境 中 创 建 三 个 不 同 的 屏 幕 布 局 创 建 了 条 件 , 这 三 个 布 局 是 : 一 个用 于 正 常 显 示 时 的 编 辑 , 另 一 个 全 屏 显 示 , 第 三 个 用 于 调 试 。
Customize( 自 定 义 ) 对 话 框
表 13-2 中是 Customize 命 令 所 显 示 的 对 话 框 , 它 们 可 以 做 如 下 事 情 :
-
增 加 或 删 除 菜 单 命 令
-
给 菜 单 命 令 增 加 图 标
-
打 开 或 关 闭 工 具 栏
-
增 加 或 删 除 工 具 栏 按 钮
-
创 建 命 名 的 敲 键 命 令
表 13-2 Customize ( 自 定 义 ) 对 话 框 中 的 设 置 , 通 过 单 击 Tools ( 工 具 ) 菜 单 中 的 Customize 来 调 出
Customize
( 自 定 义 ) 对 话 框 里 的 选 项卡
说 明
添 加 命 令 到 菜 单 ─ ─ 更 改一 个 菜 单 命 令 或 恢 复 默 认菜 单
续 表
工
具 栏 ─ ─ 使 工 具 栏 可 视或 不 可 视 , 对 工 具 栏 按 钮 启用 工
具 提 示 , 在 工 具 提 示 消
息 中 显 示 快 捷 键 , 以 及 选 择普 通 或 较 大 的 工 具 栏 按 钮
续 表
工
具 菜 单 命 令 ─ ─ 添 加 或删 除 用 于 实 用 程 序 的 命令 , 指 定
路 径 、 文 件 名 , 以
及 每 个 程 序 的 命 令 行 参数 。 指 定 实 用 程 序 的 初 始 目录
续 表
未
结 合 的 键 盘 命 令 ─ ─ 给Visual C++ 命 令 指 定 新 的 敲键
续表
宏
文 件 — — 启 用 或 禁 用 宏文 件 或 附 加 实 用 程 序
本 书 在 其 他 几 章 中 已 简 要 地 叙 述 了 这 些 命 令 , 例 如 ,第 3 章 中 演 示 了 如 何 为 名 为
WondUpperCase 和 WondlowerCase 的 两 个 未 结 合 的 命 令 指 定 敲 键 。
Customize 对 话 框 的 Commands 选 项 卡 控 制 着 环 境 菜 单 的 内 容 , 当 屏 幕 上 显 示Customize 对 话 框 时 ,可 显 示 菜 单 , 但 菜 单 中 的 每 个 单 独 命 令 并 不 处 于 有 效 状 态 。相 反 , 这 个 环 境 作 为 一 个 菜 单 编 辑 器 , 可 以 在 其 中 增 加 或 删 除 菜 单 命 令 , 改 变 命令 顺 序 , 以 及 为 现 存 命 令 增 加 图 标 。
这 儿 有 一 个 给 File 菜 单 上 的 Page Setup 命 令 增 加 图 标 图 像 的 例 子 。第 一 步 是 调 出一 个 合 适 的 图 像 , 并 存 在 剪 贴 板 中 。 可 以 使 用 屏 幕 捕 获 程 序 , 或 者 在 Visual C++
图 形 编 辑 器 中 , 自 己 设 计 一 个 16 × 16 的 图 像 。 当 然 , 也 可 以 从 任 何 环 境 工 具 栏中 捕 获 图 标 图 像 到 剪 贴 板 ,使 用 Customize 对 话 框 的 Command 选 项 卡 , 右 击 工 具栏 按 钮 , 显 示 上 下 文 菜 单 , 如 图 13-1 所 示 , 菜 单 上 的 Copy Button Image( 复 制按 钮 图 像 ) 命 令 指 按 钮 的 图 标 图 像 复 制 到 剪 贴 板 。
Customize 对 话 框 本 身 作 为 按 钮 图 像 的 便 利 来 源 , 能 显 示 Visual C++ 工 具 栏 中 的所 有 图 像 , 并 借 用 它 们 。 首 先 在 Commands 选 项 卡 中 单 击 Category 组 合 框 , 屏上 显 示 一 个 下 拉 菜 单 , 在 表 中 选 择 一 个 菜 单 , 为 菜 单 命 令 显 示 一 个 小 图 标 集 。 例如,从 Category 下 拉 表 中 选 择 File , 并 右 击 表 中 的 任 何 一 个 图 标 图 像 , 而 且 , 这个 图 标 能 够 表 达 出 页 面 设 置 的 意 思 。 所 显 示 的 上 下 文 菜 单 与 图 13-1 所 显 示 的 相同 , 从 中 , 我 们 可 选 择 Copy Button Imag e( 复 制 按 钮 图 像 ) 命 令 , 将 图 像 复 制 到剪 贴 板 。
图 13-1 当 Customize(自定义)对话框可视时,右击菜单命令或工具栏按钮,将显示这个上 下 文 菜 单
只 要 剪 贴 板 存 有 16 × 16 图 像 ,就 可 以 在 File 菜 单 上 将 其 转 换 成 Page Setup 命 令 。在 Commands 选 项 卡 中 , 在 菜 单 栏 中 单 击 File 显 示 菜 单 , 然 后 右 击 Page Setup 命 令 显 示 上 下 文 菜 单 , 如 图 13-1 所 示 。 用 Paste Button Image( 粘 贴 按 钮 图 像 ) 命 令 , 将 新 图 标 图 像 放 置 在 File 菜 单 Page Setup 命 令 的 左 侧 :
如 果 想 改 变 结 果 , 有 两 种 方 法 可 撤 消 前 面 的 工 作 。 第 一 种 是 在 Customize 对 话 框中 , 单 击 Reset All Menu s( 重 置 所 有 菜 单 ), 也 就 是 将 全 部 菜 单 命 令 全 部 恢 复 为初 始 状 态 , 如 果 仅 仅 只 想 删 除 个 别 菜 单 命 令 中 的 图 标 图 像 , 右 击 命 令 , 并 从 上 下文 菜 单 中 选 出 Text Only ( 仅 对 文 本 )。 另 外 , 要 使 用 这 种 方 法 , Customize 对 话框 必 须 显 示 出 来 。
用 户 可 以 为 菜 单 工 具 栏 本 身 增 加 图 标 图 像 , 右 击 菜 单 的 标 题 显 示 上 下 文 菜 单 ,与前 面 一 样 , 选 择 Paste Button Image ( 粘 贴 按 钮 图 像 )。 下 图 就 是 一 个 定 制 的 菜 单
栏 , 在 Visual C++ 环 境 下 , 从 不 同 位 置 中 调 出 图 标 图 像 而 创 建 的 :
要 给 环 境 菜 单 增 加 新 命 令 , 仅 需 两 步 就 能 完 成 :
-
当 Customize 对 话 框 的 Commands 选 项 卡 显 示 在 屏 上 时 , 下
拉 菜 单 。
-
从 Category 下 拉 列 表 中 选 出 需 要 的 工 具 组 , 并 将 工 具 图
标 或 命 令 名 称从 对 话 框 拖 到 菜 单 中 , 会 有 一 个 水 平 放 置 栏 指 出 菜 单 的 位 置 。
删 除 菜 单 上 的 新 命 令 时 , 右 击 命 令 , 并 从 弹 出 菜 单 中 选 击 Delete ( 删 除 ), 同 时
可 为 宏 和 未 结 合 命 令 增 加 菜 单 项 ,从 Category 下 拉 列 表 中 选 出 A ll Comman d (s 所
有 命 令 ), 并 在 表 中 定 位 命 令 名 , 然 后 , 将 命 令 名 从 表 上 移 到 菜 单 中 的 理 想 位 置 , 图 13-2 显 示 了 给 Edit 菜 单 增 加 WordUpperCase 命 令 的 过 程 。
图 13-2 将 一 条 新 命 令 放 在 一 个 菜 单 里
工 具 栏
工 具 栏 在 屏 幕 里 的 显 示 取 决 于 运 行 的 编 辑 器 , 以 及 编 辑 器 是 否 处 于 满 屏 状 态 。默认 情 况 下 , 在 满 屏 方 式 中 时 , 工 具 栏 仍 处 于 不 可 见 的 状 态 ; 要 使 工 具 栏 可 视 , 先打 开 全 屏 查 看 , 并 按 住 Alt+T 键 下 拉 Tools 菜 单 , 单 击 Customize 命 令 , 以 及Toolbars 选 项 卡 , 然 后 选 择 表 中 所 需 工 具 栏 旁 边 的 复 选 框 。 用 这 种 方 法 , 也 可 以在 全 屏 方 式 中 显 示 菜 单 栏 。 因 为 是 在 全 屏 方 式 进 行 这 种 改 变 ,工 具 栏 的 外 观 以 及
在 屏 幕 中 的 位 置 仅 适 合 于 全 屏 查 看 。 按 Esc 键 时 , 屏 幕 返 回 到 正 常 的 查 看 方 式 , 工 具 栏 也 返 回 到 原 来 位 置 , 而 且 , 它 甚 至 可 能 是 不 可 见 的 。
如 果 想 要 工 具 栏 浮 动 , 而 不 靠 着 另 一 个 窗 口 或 屏 幕 边 缘 , 在 工 具 栏 中 双 击 任 何 空白 区 。 要 使 一 个 浮 动 工 具 栏 返 回 到 原 来 停 靠 的 位 置 , 双 击 工 具 的 标 题 栏 即 可 。 在第 1 章 中 我 们 提 到 过 The Environment 工 具 栏 , 在 拖 动 浮 动 工 具 栏 时 , 按 住 Ctrl 键 , 以 防 它 停 靠 在 另 一 个 窗 口 。 当 拖 动 工 具 栏 到 停 靠 位 置 时 , 按 住 Shift 键 , 可以 在 水 平 和 垂 直 方 向 之 间 切 换 工 具 栏 窗 口 。
Visual C++ 能 轻 而 易 举 地 改 变 工 具 栏 的 外 观 和 内 容 , 故 能 将 工 具 栏 按 钮 从 一 个 工具 栏 复 制 到 另 一 个 工 具 栏 上 , 并 保 存 使 用 的 工 具 所 在 的 工 具 栏 中 。 首 先 , 显 示Customize 对 话 框 , 在 这 里 , 可 用 Tools 菜 单 , 或 右 击 工 具 栏 , 从 它 的 上 下 文 菜单 中 选 出 Customize 。 如 果 想 要 修 改 的 工 具 栏 没 有 显 示 在 屏 幕 上 , 单 击 一 下Toolbars 选 项 卡 ,工 具 栏 就 会 显 示 出 来 。在 对 话 框 的 Commands 按 钮 中 ,从 Category 框 中 选 择 一 个 菜 单 名 , 来 显 示 图 示 所 选 菜 单 上 属 于 命 令 的 工 具 按 钮 。单 击 对 话 框中 的 工 具 按 钮 , 来 显 示 关 于 这 个 工 具 的 简 短 描 述 , 从 而 确 定 按 钮 的 作 用 。 为 确 保目 标 工 具 栏 和 Customize 对 话 框 不 会 在 屏 幕 上 重 叠 , 可 将 想 要 的 工 具 从 对 话 框 拖到 工 具 栏 中 , 只 要 用 户 乐 意 , 也 可 在 环 境 菜 单 栏 中 设 置 工 具 按 钮 。
如 同 菜 单 命 令 一 样 , 当 Customize 对 话 框 显 示 在 屏 幕 上 时 , 工 具 栏 按 钮 不 再 起 作用 。 这 就 要 求 用 户 拖 动 工 具 栏 按 钮 , 并 将 它 放 在 相 同 工 具 栏 的 不 同 位 置 , 或 拖 动这 个 按 钮 到 不 同 的 工 具 栏 。 当 然 , 也 可 通 过 左 右 移 动 按 钮 , 在 两 个 按 钮 间 插 入 约
半 个 按 钮 宽 度 的 空 间 。 完 成 上 述 过 程 后 , 单 击 对 话 框 中 的 Close 按 钮 。 实 际 上 , 在 把 一 个 工 具 从 一 个 工 具 栏 移 到 另 一 个 工 具 栏 中 并 不 需 要 Customize 对话框。显示 工 具 栏 , 按 住 Alt 键 , 将 想 要 的 工 具 按 钮 从 一 个 工 具 栏 移 到 另 一 个 工 具 栏 中 , 当 然 , 也 可 以 同 时 按 住 Alt 和 Ctrl 键 来 复 制 按 钮 , 而 不 是 移 动 它 。
自 定 义 工 具 栏
根 据 个 人 的 口 味 , 如 果 某 些 较 大 的 工 具 栏 占 据 了 屏 幕 较 大 的 区 域 , 我 们 会 希 望Visual C++ 能 提 供 一 两 个 小 工 具 栏 , 并 能 将 它 们 放 在 屏 幕 角 落 里 , 里 面 只 有 确 实需 要 的 几 个 工 具 。 要 想 实 现 上 面 的 想 法 , 我 们 可 以 自 定 义 工 具 栏 , 而 且 这 只 需 要很 少 的 几 步 就 能 完 成 。 本 书 第 3 章 曾 介 绍 过 自 定 义 工 具 栏 的 内 容 , 并 用WordupperCase 和 WordLowerCase 两 个 命 令 进 行 演 示 过 。下 面 将 前 述 内 容 进 行 补充 说 明 。
首 先 , 根 据 在 何 处 使 用 工 具 栏 , 来 选 定 标 准 或 全 屏 的 显 示 方 式 , 然 后 在 Tools 菜单 上 单 击 Worduppercase 一 类 的 未 结 合 命 令 ,在 Category 框 中 选 出 A ll Commands
( 所 有 命 令 ) 来 显 示 命 令 名 称 表 , 在 表 中 确 定 所 要 的 命 令 , 然 后 拖 动 表 项 到 工 具栏 中 。 要 复 制 预 先 规 定 的 环 境 按 钮 时 , 在 Category 表 中 选 出 一 个 菜 单 , 将Customize 对 话 框 的 某 个 按 钮 移 到 工 具 栏 中 。
不 必 用 预 先 确 定 的 工 具 栏 来 接 收 这 个 新 的 工 具 按 钮 ,原 因 是 环 境 提 供 了 两 种 方 法来 创 建 定 制 工 具 栏 。 第 一 种 方 法 是 在 Customize 对 话 框 的 Toolbars 选 项 卡 中 单 击New 按 钮 , 然 后 给 新 工 具 栏 命 名 :
单 击 O K 按 钮 时 , Visual C++ 就 会 创 建 可 存 放 前 述 按 钮 的 空 白 工 具 栏 。
与 第 一 种 方 法 比 较 起 来 , 另 一 种 方 法 更 易 于 创 建 新 工 具 栏 , 只 须 从 Customize 对话 框 中 拖 动 一 个 命 令 , 并 将 它 放 到 屏 幕 上 不 会 被 工 具 栏 覆 盖 住 的 任 何 区 域 即 可 。对 于 WordUpperCase 一 类 的 新 命 令 来 说 , 只 要 从 Category 列 表 中 选 出 A ll Commands , 并 从 Commands 表 中 拖 出 适 当 的 表 项 。 这 个 过 程 和 前 面 将Worduppercase 存 放 在 菜 单 上 一 样 。 在 Button Appearance ( 按 钮 外 观 ) 对 话 框 中选 择 一 个 按 钮 图 标 ,增 加 适 当 的 正 文 来 标 识 这 个 按 钮 ,单 击 O K 按 钮 ,Visual C++ 就 会 自 动 创 建 一 个 新 工 具 栏 来 存 放 这 个 按 钮 ;
为 了 复 制 已 确 定 的 环 境 命 令 ,可 在 Category 表 中 选 出 一 个 菜 单 名 ,并从 Customize 对 话 框 中 移 出 一 个 图 标 , 放 在 屏 幕 上 的 空 白 区 , 如 图 13-1 所 示 。 这 种 方 法 的 最大 好 处 在 于 能 同 时 复 制 图 标 图 像 和 按 钮 正 文 。 要 显 示 新 工 具 栏 按 钮 的 图 像 和 正
文 ,右 击 工 具 栏 中 的 按 钮 ,并 从 上 下 文 菜 单 中 选 出 Image And Te x (t 图 像 和 文 本 )。
图 13-3 创 建 一 个 新 的 自 定 义 工 具 栏
可 以 轻 而 易 举 地 从 任 何 工 具 栏 中 删 除 按 钮 , 不 管 这 些 按 钮 是 自 定 义 的 ,还 是 预 定义 的 。 当 屏 幕 上 显 示 Customize 对 话 框 时 , 我 们 可 以 从 工 具 栏 中 拖 出 一 个 按 钮 , 并 在 屏 上 的 空 白 处 释 放 它 。 同 时 , 也 可 为 工 具 栏 和 任 何 按 钮 标 题 更 名 。 给 按 钮 标题 更 名 时 , 先 在 它 所 在 的 工 具 栏 中 右 击 这 个 按 钮 , 并 从 弹 出 菜 单 中 选 择 Button
Appearanc e(按钮外观)命令,然后,在 Button Appearance 对 话 框 底 部 的 编 辑 框中 重 新 输 入 按 钮 标 题 , 当 然 , 也 可 在 任 何 时 候 , 通 过 剪 贴 板 的 剪 切 和 粘 贴 操 作 来更 换 按 钮 图 标 。
给 定 制 的 工 具 栏 更 名 时 , 有 必 要 先 浏 览 一 下 Customize 对 话 框 中 的 Toolbars 选 项卡 , 在 这 里 , 值 得 注 意 的 是 , 用 户 只 能 给 自 定 义 的 工 具 栏 更 名 , 而 不 能 给 系 统 预定 义 的 Developer Studio 工 具 栏 更 名 。 更 名 时 , 先 从 表 中 选 出 自 定 义 的 工 具 栏 , 并 在 Toolbar Nam e( 工 具 栏 名 称 ) 框 中 输 入 新 的 名 称 , 新 的 名 称 就 会 在 工 具 栏 的标 题 区 域 和 Customize 对 话 框 的 Toolbar 表 中 显 示 出 来 , 这 样 , 我 们 就 能 以 相 同的 方 式 打 开 和 关 闭 其 他 工 具 栏 。 当 新 工 具 栏 只 有 几 个 按 钮 时 , 应 尽 量 名 称 简 短 , 否 则 , 标 题 栏 中 的 空 间 会 不 够 用 。 图 13-4 显 示 了 在 第 3 章 中 创 建 从 Toolbar1 到Word Case 工 具 栏 的 更 名 过 程 。
图 13-4 在 Customize( 自 定 义 ) 对 话 框 中 重 新 命 名 一 个 工 具 栏
在 表 中 选 择 一 个 定 制 工 具 栏 ,并 启 用 Delete 按 钮 ,使 它 具 有 永 久 删 除 定 制 工 具 栏的 功 能 。
添 加 命 令 到 Tools ( 工 具 ) 菜 单
本 书 第 3 章 中 的 最 后 一 节 , 讲 述 了 在 Developer Studio 环 境 下 , 添 加 命 令 到 Tools 菜 单 中 , 以 便 启 动 第 三 方 文 本 编 辑 器 。 并 讲 述 了 不 仅 可 为 文 本 编 辑 器 , 也 可 为 任何 程 序 增 加 命 令 。本 节 更 详 细 地 论 述 了 增 加 命 令 的 过 程 , 以 及 集 成 应 用 程 序 到 环境 中 的 可 用 选 项 。
首 先 , 让 我 们 在 没 有 提 示 或 专 门 处 理 工 具 的 情 况 下 , 回 顾 一 下 给 Tools 菜 单 增 加简 单 的 实 用 程 序 的 过 程 。 M fcTrees3 应 用 程 序 列 出 了 MFC 类 的 层 次 , 它 是 第 5 章 中 开 发 的 MfcTree2 对 话 框 的 更 完 整 的 版 本 。 与 先 前 的 版 本 相 比 , M fcTree3 的源 代 码 并 无 多 大 改 变 , 但 此 程 序 现 在 将 类 名 作 为 字 符 串 资 源 来 保 存 ,而 不 是 在 对话 的 OnInitDialog 函 数 中 ,将 它 们 硬 编 码 到 CTreeCtrl::InsertItem 的 长 长 的 调 用 列表 中 。在 配 套 光 盘 的 Chapter.13\ M fcTree3 文 件 夹 中 , 可 以 找 到 所 有 源 文 件 , 在 下面 的 演 示 中 ,只 需 要 将 CD 盘 上 的 M fcTree3.exe 程 序 文 件 复 制 到 磁 盘 上 的 任 何 位置 。
通 过 调 用 Customize 对 话 框 , 可 以 添 加 M fcTrees 工 具 到 环 境 中 。 在 Tools 选 项 卡中 , 双 击 新 项 所 在 的 框 , 它 在 列 表 最 后 的 工 具 之 后 , 显 示 为 一 个 点 划 的 矩 形( 如
图 13-5 所 示 )。 输 入 菜 单 项 文 本 &MFCTree Lis t, 用 字 母 “ M ” 作 为 命 令 助 记 符 , 来 指 定 菜 单 项 。
图 13-5 添加新的 MFCTree List 命令到 Tools ( 工 具 ) 菜 单
按 下 Enter 键 , 在 Command 框 中 , 为 M fcTree3 输 入 完 整 的 路 径 和 文 件 名 , 包 括EXE 扩 展 名 。 在 关 闭 Customize 对 话 框 时 , Visual C++ 将 此 命 令 添 加 到 Tools 菜单 的 底 部 。 单 击 新 的 MFC Tree List 命 令 便 启 动 M fcTree3 应 用 程 序 , 它 将 显 示MFC 的 类 列 表 , 如 图 13-6 所 示 。
M fcTree3 可 以 很 容 易 地 添 加 到 Tools 菜 单 中 , 因 为 此 程 序 不 需 要 任 何 命 令 行 参数 , 而 且 在 一 个 单 独 的 窗 口 即 可 显 示 其 输 出 。 下 面 将 介 绍 的 其 他 实 用 程 序 就 不 这么 简 单 了 。
图 13-6 Tools ( 工 具 ) 菜 单 和 MFCTree3 应 用 程 序 的 新 的 MFC Tree List 命令
命 令 行 参 数
某 些 应 用 程 序 , 尤 其 是 基 于 控 制 台 的 程 序 , 在 程 序 运 行 时 , 需 要 使 用 用 户 指 定 的
命 令 栏 参 数 。 对 从 Tools 菜 单 上 启 动 使 用 命 令 行 参 数 的 程 序 , 有 两 种 方 法 。 第 一种 是 给 Visual C++ 配 置 每 次 运 行 程 序 时 查 询 命 令 行 参 数 的 功 能 。可 选 用 Customize 对 话 框 中 的 Prompt For Argument s( 提 示 参 数 )复 选 框 来 设 置 这 种 配 置 ,如 图 13-5 所 示 。 具 有 这 种 配 置 后 , 在 每 次 从 Tools 菜 单 运 行 此 实 用 程 序 时 , 此 复 选 框 会 使Visual C++ 提 示 参 数 , 然 后 , 通 过 命 令 行 将 参 数 传 给 程 序 。
我 们 看 一 下 范 例 : 假 定 用 户 想 浏 览 一 下 Prompt For Arguments 复 选 框 打 开 时 , 添加 W indows Notepad 实 用 程 序 到 Tools 菜 单 中 , 将 出 现 的 情 况 。 在 Tools 菜 单 中启 动 Notepad ,首 先 ,显 示 一 个 提 示 ,你 可 以 在 其 中 输 入 文 件 名 ,如 图 13-7 所 示 。单 击 O K 按 钮 , Notepad 启 动 , 并 自 动 装 入 指 定 的 文 件 。
如 果 用 户 想 要 在 每 次 启 动 应 用 程 序 时 ,都 能 接 收 到 同 一 命 令 行 参 数 ,使 用 第 二 种方 法 来 提 供 参 数 更 为 简 捷 。清除 Prompt For Arguments 复 选 框 ,在 图 13-5 所 示 的A rguments 框 中 输 入 命 令 行 参 数 。 此 后 , Visual C++ 会 将 参 数 传 递 给 程 序 , 而 且不 再 提 示 。
图 13-7 当 从 工 具 菜 单 运 行 程 序 时 的 命 令 行 变 量 提 示
参 数 宏
Visual C++ 具 有 参 数 宏 这 一 较 显 著 的 特 征 , 这 样 , 给 Tools 程 序 传 递 命 令 更 为 容易 一 些 。 如 表 13-3 所 示 。 每 一 条 宏 命 令 都 扩 展 成 一 个 字 符 串 , 用 来 描 述 当 前 项目 或 文 件 的 特 点 。
在 说 到 参 数 宏 命 令 的 灵 活 性 时 ,我 们 来 看 一 个 简 单 的 例 子 ,假 定 用 户 想 用 Notepad 工 具 , 使 当 前 运 行 的 文 本 编 辑 器 中 的 文 档 处 于 常 开 状 态 , 使 用 宏 命 令 $(FilePath) 要 容 易 得 多 , 且 不 用 每 次 都 提 示 文 件 名 , 即 可 达 到 上 述 目 的 。 同 时 , 这 个 宏 命 令也 能 扩 展 , 对 当 前 具 有 输 入 焦 点 文 档 使 用 完 全 的 路 径 和 文 件 名 。如 若 没 有 文 档 焦点 ,宏 将 产 生 一 个 空 字 符 串 。要 利 用 Notepad 工 具 来 使 用 宏 命 令 ,可 删 除 Customize
A rguments 对 话 框 中 的 Prompt For Arguments 复 选 框 , 并 键 入 $(FilePath),如下所示 :
表 13-3 适 用 于 Tools 选 项 卡 的 参 数 宏宏 名 称 说 明
$(CurCol)
$(CurDir)
$(CurLine)
$(CurText)
$(FileDir)
$(FileExt)
当 前 列当 前 目 录当 前 行当 前 文 本文 件 目 录
文 件 扩 展
名
在 文 本 窗 口 里 , 插 入 符 位 置 的 当 前 列 数当 前 工 作 目 录 , 表 示 为 d:path\
在 文 本 窗 口 里 , 插 入 符 位 置 的 当 前 行 数
当 前 文 本 , 插 入 符 所 停 位 置 的 词 , 或 者 所 选文 本 的 一 行
活 动 窗 口 里 的 源 文 件 目 录 , 表 示 为 d:path\
活 动 窗 口 里 的 源 文 件 扩 展 名
续 表
$(TargetArgs
)
$(TargetDir)
$(TargetExt)
$(TargetNam e)
$(TargetPath)
$(WkspDir)
$(WkspNam e)
目标参数目标目录
目标扩展名目 标 名
目标路径
工 作 空 间 目录
工作空间名
传递给项目应用程序的命令行参数
Debug 或 Release 子 目 录 里 包 含 的 可 执 行 项 目 的 路径,表示为 d:\path\
可 执 行 项 目 的 文 件 名 范 围 , 例 如 EXE 或 DLL
可执行项目的文件名 (通常是项目名)
可执行项目的文件名的完整路径和文件名,表示为
d:\path\filename
含 有 项 目 文 件 的 目 录 , 表 示 为 d:\path \项 目项 目 名
在 文 本 编 译 器 中 编 辑 文 档 期 间 ,启 动 此 工 具 时 ,Notepad 会 自 动 打 开 相 同 的 文 档 。
表 13-3 中 提 供 了 Tools 选 项 卡 中 可 用 的 15 种 不 同 的 参 数 宏 命 令 。
在 Visual C++ 查 询 命 令 行 参 数 时 , 可 使 用 参 数 宏 命 令 来 完 成 , 如 图 13-7 所 示 。而 且 , 在 Arguments ( 参 数 ) 和 Initial Directory( 初 始 目 录 ) 文 本 框 中 , 这 种 宏命 令 更 为 有 用 。 我 们 在 使 用 这 些 宏 命 令 时 , 不 必 费 力 去 记 住 它 们 , 需 要 时 只 要 单击 一 下 文 本 框 旁 边 的 箭 头 按 钮 ,就 会 显 示 一 个 菜 单 , 其 中 是 宏 命 令 名 称 的 完 整 列表 。单 击 表 中 的 任 一 宏 命 令 ,它 就 会 出 现 在 旁 边 的 文 本 框 中 。宏 名 不 区 分 大 小 写 , 例 如 , $(FileDir) 与 $(filedir) 是 相 同 的 , 宏 所 产 生 的 路 径 字 符 串 , 诸 如 $(FileDir) 与
$(TangetDir) , 以 反 斜 线 结 束 。
例 子 : Struc t实 用 程 序 工 具
参 数 宏 命 令 能 为 Developer Studio 环 境 与 实 用 程 序 更 紧 密 地 集 成 到 一 起 提 供 一 种方 式 。 通 过 自 动 为 当 前 项 目 或 文 档 提 供 合 适 的 命 令 行 命 令 , 利 用 参 数 宏 , 可 以 为Visual C++ 专 门 设 计 的 Tools 菜 单 创 建 附 加 的 实 用 程 序 。
这 里 有 一 个 例 子 , 说 明 了 工 具 程 序 是 怎 样 响 应 在 文 本 编 辑 器 中 的 当 前 插 入 符 位置 ,甚 至 显 示 Developer Studio 环 境 内 部 的 输 出 。Struct 实 用 程 序 描 述 了 接 收 命 令行 参 数 的 过 程 , 而 这 些 参 数 包 含 来 自 当 前 文 本 文 档 中 的 词 。如 果 参 数 容 纳 此 程 序小 数 据 库 中 表 示 的 W in32 API 结 构 的 一 个 名 称 , Struct 就 会 在 Developer Studios 的 Output 窗 口 中 显 示 结 构 的 声 明 。 $(CurText) 宏 产 生 的 命 令 行 参 数 可 以 在 文 档 中去 选 择 , 如 果 没 有 选 择 文 本 , 该 词 就 会 在 插 入 符 位 置 。 如 果 Struct 不 能 识 别 所 提
供 的 结 构 名 称 , 屏 幕 上 将 会 出 现 未 知 结 构 名 的 消 息 。
图 13-8 显 示 了 在 Customize 对 话 框 中 怎 样 创 建 Struct 程 序 , 并 使 它 成 为 Tools 菜单 上 的 一 个 工 具 的 过 程 。 Struct 可 以 访 问 到 Output 窗 口 , 原 因 是 Use Output W indow (使 用 输 出 窗 口 ) 复 选 框 处 于 打 开 状 态 。这 个 复 选 框 只 对 基 于 控 制 台 的 程 序启 用 。 设 置 此 复 选 框 , 能 使 Develop Studio 截 取 所 有 实 用 程 序 的 标 准 输 出 , 并 在Output 窗 口 中 单 独 的 选 项 卡 里 显 示 。通 过 选 项 卡 中 显 示 的 工 具 名 称 ,我们可以确定 输 出 来 源 。
Struct 程 序 相 当 简 单 ,仅 能 识 别 几 个 W in32 API 结 构 ,例 如 RECT 和 POIN I。Struct 是 以 C 语 言 编 写 的 , 只 不 过 是 一 个 程 序 外 壳 , 但 是 , 它 也 能 很 容 易 地 扩 充 , 以包 括 附 加 的 结 构 和 其 他 信 息 , 如 函 数 原 型 以 及 消 息 参 数 。 尽 管 具 有 某 些 局 限 性 , 这 个 程 序 还 是 能 清 楚 地 演 示 如 何 将 这 些 工 具 和 Visual C++ 集 成 。 安 装 和 检 测Struct 实 用 程 序 时 , 可 采 取 以 下 步 骤 :
-
如 果 从 配 套 CD 盘 上 复 制 项 目 时 , 没 有 运 行 Setup 程 序 ,
我 们 可 复 制Struct.exe 程 序 文 件 复 制 到 硬 盘 的 适 当 位 置 。 此 程 序 装 在 CD 的Code\Chapter.13\Struct\Release 子 文 件 夹 中 。
-
在 Customize 对 话 框 的 Tools 选 项 卡 中 , 给 Tools
菜单,为Struct 添 加命 令 , 如 图 13-8 所示。在 Command 框 中 , 输 入 Struct.exe 的 路 径 和 文件 名 , 包 括 EXE 扩 展 名 , 在 硬 盘 上 指 定 文 件 夹 到 复 制 此 程 序 的 位 置 。在Arguments 框 中 输 入 $(CurText) , 或 单 击 旁 边 的 箭 头 按 钮 , 并 从 表 中
选 择 Current Tex t( 当 前 文 本 )。 注 意 , 在 此 过 程 中 , 必 须 打 开 对 话 框左 下 角 的 Use Output Window 复 选 框 。
图 13-8 为 Struct 实用程序设置一个菜单命令
- 在 文 本 编 辑 器 中 打 开 Struet.c 源 文 件 ( 列 表 13-1 ),或用
File 菜 单 上 的
New 命 令 , 来 创 建 一 个 新 文 本 文 档 。 在 新 建 文 档 中 输 入 一 行 , 其 中 包含 Struct 所 能 识 别 的 API 结 构 名 称 , 如 下 :
RECTRECTLPOINT#POINTL#SIIE#POINIS#FILE#TIME#SYSTE#MTIME
- 在 文 档 中 , 将 插 入 符 放 在 函 数 名 中 的 任 一 位 置 , 在
Tools 菜 单 上 单 击W in32 Structure 命令,图 13-9 显 示 了 Output 窗 口 中 W in32 Struct 选 项卡 的 实 用 程 序 消 息 。
图 13-9 在 Outpu t( 输 出 ) 窗 口 里 , Struct 实 用 程 序 的 输 出 出 现 在 自 己 的 选 项 卡 中
列 表 13-1 Struct.c 源 文 件
// Struct.c Displays Win32 structure declarations in Output window
// Copyright (c) 1998, Beck Zaratian
#include <stdio.h>
#include <string.h>
char *pszStruct[] =
{
"FILETIME\n{\n DWORD dwLowDateTime;\n DWORD " \
"dwHighDateTime;\n}",
"OVERLAPPED\n{\n DWORD Internal;\n DWORD " \
"InternalHigh;\n DWORD Offset;\n DWORD " \
"OffsetHigh;\n HANDLE hEvent;\n};",
"POINT\n{\n LONG x;\n LONG y;\n};",
"POINTL\n{\n LONG x;\n LONG y;\n};",
"POINTS\n{\n SHORT x;\n SHORT y;\n};",
"PROCESS_INFORMATION\n{\n HANDLE hProcess;\n HANDLE " \
"hThread;\n DWORD dwProcessId;\n DWORD dwThreadId;\n};",
"RECT\n{\n LONG left;\n LONG top;\n LONG right;" \
"\n LONG bottom;\n};",
"RECTL\n{\n LONG left;\n LONG top;\n LONG right;" \
"\n LONG bottom;\n};",
"SECURITY_ATTRIBUTES\n{\n DWORD nLength;\n LPVOID " \
"lpSecurityDescriptor;\n BOOL bInheritHandle;\n}",
"SIZE\n{\n LONG cx;\n LONG cy;\n};",
"SYSTEMTIME\n{\n WORD wYear;\n WORD wMonth;\n WORD " \
"wDayOfWeek;\n WORD wDay;\n WORD wHour;\n WORD " \
"wMinute;\n WORD wSecond;\n WORD wMilliseconds;\n};"
};
main( int argc, char *argv[], char *envp[] )
{
char *sz;
size_t iLen;
int i, iCount = sizeof(pszStruct)/sizeof(char*);
if (argc > 1)
{
for (i=0; i < iCount; i++)
{
// Determine length of structure name
sz = pszStruct[i];
iLen = (size_t) (strchr( sz, '\n' ) - sz);
// If structure is in database, display declaration
if (strlen( argv[1] ) == iLen &&
!strncmp( sz, argv[1], iLen ))
{
printf( "Structure declaration:\n\n" );
printf( "struct %s\n\n", pszStruct[i] );
break;
}
}
if (i == iCount)
printf( "Structure not recognized\n\n" );
}
return 0;
}
宏
Visual C++ 中 有 一 种 优 秀 的 宏 语 言 , 其 格 式 为 Visual Basic Scripting Edition , 即 著名 的 VBScrip t。通 过 在 文 件 中 存 储 宏 脚 本 时 , 这 个 环 境 能 使 不 断 收 集 与 其 他 文 件共 享 的 宏 命 令 。 通 过 记 录 任 务 序 列 , 或 者 是 书 写 程 序 脚 本 , 用 户 可 创 建 一 个 宏 。在 这 部 分 里 , 我 们 将 看 到 , VBScript 提 供 了 一 个 函 数 库 , 它 可 以 运 行 宏 来 查 询 用户 的 输 入 , 显 示 消 息 框 , 进 行 算 术 运 算 , 控 制 字 符 串 , 并 执 行 一 些 超 出 用 户 接 口程 序 范 围 的 其 他 任 务 。
VBScript 是 M icrosoft Visual Basic for Applicati o (n VBA )的 子 集 ,VBA 是 M icrosoft
Access 这 样 程 序 的 编 程 语 言 。 VBScript 设 计 成 一 种 用 H T M L 书 写 的 Web 文 档 的脚 本 编 程 语 言 , 它 为 H T M L 页 面 提 供 了 嵌 入 ActiveX 控 件 和 其 他 对 象 的 方 式 。但 是 , 它 还 可 以 作 为 常 规 的 脚 本 编 程 语 言 , 能 解 释 针 对 应 用 程 序 的 命 令 表 , 并 通过 Automation 来 执 行 这 些 命 令 。 换 句 话 说 , VBScript 可 以 作 为 宏 语 言 。 在 这 种环 境 中 , Visual C++ 能 使 用 VBScrip t。
一 个 宏 意 味 着 将 一 类 命 令 封 装 为 一 个 命 令 。 用 VBScript 写 的 宏 实 际 上 是 一 个 简单 程 序 , 在 这 种 程 序 中 , 宏 脚 本 作 为 源 代 码 。 执 行 一 个 宏 , 就 等 于 执 行 了 脚 本 中包 含 的 所 有 命 令 。 在 第 3 章 中 , 我 们 讲 了 怎 样 通 过 记 录 敲 键 和 鼠 标 单 击 , 来 创 建VBScript 宏 的 过 程 。 记 录 后 , 便 为 用 户 提 供 一 个 宏 , 它 可 以 回 放 一 系 列 记 录 的 命令 。 一 旦 记 录 了 一 个 宏 , 就 需 要 手 工 使 用 这 些 命 令 。 此 后 , 无 论 在 什 么 时 候 运 行录 制 的 宏 , Developer Studio 都 会 自 动 重 复 相 同 的 步 骤 。
第 3 章 中 的 解 除 制 表 符 转 换 宏 是 使 用 Record Quick Macro ( 快 速 录 制 宏 ) 命 令 创建 的 , 这 一 方 法 尽 管 很 方 便 , 但 也 有 其 不 足 之 处 , 即 Record Quick Macro 命 令 仅能 创 建 一 个 临 时 宏 , 当 下 次 使 这 个 命 令 和 记 录 另 一 个 宏 时 , 前 一 个 宏 将 永 远 消失 。 相 反 , Tools 菜 单 中 的 Macro 命 令 能 记 录 永 久 保 存 的 宏 , 并 用 一 个 单 独 的 文件 来 保 存 几 个 相 关 的 宏 。为 了 说 明 这 点 , 这 里 说 明 了 怎 样 创 建 这 个 宏 的 永 久 性 版本 , 并 用 相 应 的 制 表 符 转 换 宏 来 增 强 它 。 这 个 宏 扩 展 了 TabifySelection 和UntabifySelection 命 令 , 来 对 整 个 文 档 加 制 表 符 和 取 消 制 表 符 , 而 不 仅 仅 选 择 的文 本 。 第 一 步 是 创 建 一 个 宏 文 件 , 接 着 为 这 些 新 宏 增 加 脚 本 。 在 文 件 编 辑 器 中 打开 一 个 文 档 ,从 Tools 菜 单 中 选 取 Macro 命 令 ,来 打 开 M acro 对 话 框 ,单 击 Options 按 钮 ,接 着 单 击 New Fil e( 新 文 件 )按 钮 ,输 入 一 个 文 件 名 和 这 个 宏 文 件 的 说 明 , 如 下 :
单 击 O K 按 钮 ,并 在 第 一 个 宏 的 名 下 键 入 UntabifyAll ,单 击 Record 按 钮 ,在 Add M acr o( 添 加 宏 ) 对 话 框 中 键 入 可 选 的 说 明 文 字 , 例 如 , Expands tabs into spaces 。单 击 O K 按 钮 , 主 M acro 对 话 框 关 闭 , 并 返 回 到 文 本 编 辑 器 的 文 档 中 。 现 在 , Record 工 具 栏 显 示 在 屏 上 , 鼠 标 光 标 中 包 含 着 盒 式 磁 带 的 图 像 , 这 就 暗 示 着 ,现在 , Visual C++ 正 在 记 录 着 每 个 敲 键 和 鼠 标 单 击 。 同 第 3 章 所 述 的 一 样 , 我 们 可
以 通 过 同 样 的 三 步 来 创 建 宏 ;
1 在 Edit 菜 单 上 , 单 击 Select All ( 全 选 ) 命 令 来 选 整 个 文 档 。2 从 Edit 菜 单 中 选 Advanced , 并 单 击 Untabify Selection 命 令 。
3 按 Ctrl+Home 将 插 入 符 移 到 文 档 顶 端 。
在 Record 工 具 栏 中 单 击 Stop Recording 按 钮 来 结 束 录 制 ,在 此 处 ,文 本 编 辑 器 自动 打 开 新 的 VBScript 宏 文 件 , 该 文 件 名 为 Tabs.dsm , 此 时 可 以 编 辑 宏 ( 该 文 件的 扩 展 名 代 表 Developer Studio 宏 )。在 Window 菜 单 上 单 击 文 件 名 , 就 会 返 回 到原 来 的 文 本 文 档 中 。
当 再 次 显 示 Macro 对 话 框 时 , Macro Fil e( 宏 文 件 ) 组 合 框 中 就 会 出 现 Tabs 文 件名 。 按 上 文 所 述 的 相 同 步 骤 , 可 增 加 一 个 新 的 转 换 制 表 符 宏 , 只 是 这 次 不 单 击Options 和 New File 按 钮 , 因 为 我 们 只 想 增 加 到 宏 文 件 , 而 不 是 去 创 建 一 个 新 文件 。仅 需 键 入 TabifyAll 来 给 第 二 个 宏 命 名 ,单 击 Record 按 钮 ,输 入 新 宏 的 说 明 , 例 如 Converts spaces into tabs 。 接 着 按 以 上 步 骤 , 创 建 第 二 个 宏 , 这 次 , 我 们 选用 Tabify Selectio n( 将 选 择 内 容 进 行 制 表 符 转 换 ) 命 令 。此 Tabs 宏 文 件 现 在 包 含着 两 个 宏 , 即 在 M acro 对 话 框 中 列 出 的 UntabifyAll 和 TabifyAll 。 运 行 宏 时 , 从对 话 框 的 列 表 中 选 出 它 , 并 单 击 Run 按 钮 , 其 效 果 与 人 工 重 新 键 入 录 制 的 敲 键一 样 。
在 录 制 过 程 中 , Visual C++ 将 所 执 行 的 每 个 命 令 列 表 进 行 编 译 , 并 把 它 们 用VBSCript 代 码 写 到 DSM 文 件 中 。 在 录 制 时 , 可 以 在 任 何 编 辑 器 中 工 作 , 甚 至 可以 在 编 辑 器 之 间 来 回 转 换 。 单 击 Record 工 具 栏 中 的 第 二 个 按 钮 暂 停 录 制 , 可 以执 行 不 包 括 在 最 终 宏 中 的 操 作 。 再 单 击 相 同 的 按 钮 , 则 恢 复 录 制 。 一 般 来 说 , 在运 行 宏 前 , 首 先 , 应 把 插 入 符 移 到 要 回 放 所 录 制 敲 键 的 文 档 位 置 。 这 一 步 对
TabifyAll 和 UntabifyAll 宏 来 说 , 并 不 需 要 。
大 多 数 的 宏 都 是 通 过 这 种 方 法 来 完 成 录 制 一 系 列 命 令 的 任 务 ,但 是 ,可 能 出 现 这种 状 况 : 需 要 一 个 宏 完 成 的 任 务 却 不 能 被 录 制 。 要 创 建 这 样 一 个 宏 , 必 须 写 一 个脚 本 , 并 将 它 作 为 DSM 文 件 存 盘 , 下 节 将 介 绍 如 何 做 这 一 工 作 。
例 子 : 用 于 栏 目 搜 索 和 替 换 的 宏
VBScript 包 括 一 个 编 译 器 和 一 个 运 行 库 。 VBScript 编 译 器 解 释 宏 中 列 出 的 命 令 , 库 提 供 了 宏 可 以 调 用 的 函 数 。 附 录 C 中 包 含 了 关 于 VBScript 的 简 短 教 程 , 它 描述 了 语 言 元 素 和 VBScript 库 函 数 。 VBScript 十 分 简 单 , 可 以 很 快 掌 握 , 好 在 它的 许 多 编 程 特 点 和 C 语 言 十 分 相 似 。
使 用 一 个 简 单 例 子 即 可 说 明 一 些 VBScript 宏 的 特 征 。 在 第 3 章 中 讲 述 过 , 虽 然在 一 个 文 档 中 可 以 选 择 一 块 文 本 ,但 文 本 编 辑 器 对 所 选 栏 没 有 提 供 搜 索 和 替 换 操作 的 方 法 。 如 果 给 文 本 栏 作 上 标 记 , 并 从 Edit ( 编 辑 ) 菜 单 中 选 择 Replace ( 替换 ), Replace 对 话 框 就 会 使 Selection 单 选 钮 失 效 。 对 编 辑 器 来 说 , 在 标 记 栏 中替 换 文 本 , 是 非 常 有 用 的 特 征 。 用 一 个 宏 , 我 们 能 编 程 Developer Studio 文 本 编辑 器 来 做 到 这 一 点 。
通 过 尽 可 能 多 地 进 行 录 制 来 创 建 宏 , 然 后 , 使 用 文 本 编 辑 器 , 来 给 宏 脚 本 文 件 增加 一 些 不 能 录 制 的 命 令 , 这 是 十 分 方 便 的 。 在 本 例 中 , 我 们 不 录 制 任 何 东 西 , 原
因 是 Replace.dsm 宏 文 件 已 经 从 头 开 始 编 码 了 。 要 检 测 一 下 这 个 宏 , 将 CD 盘 上的 Chapter.13 文 件 夹 中 的 Replace.dsm 复 制 到 硬 盘 的 Visual C++ Macro 文 件 夹中 。 该 文 件 夹 的 默 认 路 径 是 Common\MsDev98\Macros 。
从 Tools 菜 单 中 选 M acro 命 令 , 单 击 Options 按 钮 , 然 后 单 击 Loaded File s( 加 载的 文 件 )按 钮 。这 就 会 显 示 我 们 熟 悉 的 Customize 对 话 框 ,其 中 ,新 的 Replace.dsm 文 件 显 示 在 Macros 文 件 夹 中 的 宏 文 件 表 中 , 打 开 表 中 的 Replace 复 选 框 , 关 闭对 话 框 , 并 再 一 次 调 用 Macro 命令。这个 Replace 宏 文 件 马 上 显 示 在 Macro 对 话框 的 下 拉 列 表 中 , 如 图 13-10 所 示 , 选 择 这 个 文 件 , 在 表 中 增 加 单 独 的ColumnarReplace 宏 。
图 13-10 调用 ColumnarReplace 宏
如 果 想 亲 自 键 入 这 个 宏 , 用 New 命 令 , 并 在 Files 选 项 卡 中 双 击 Macro File ( 宏文 件 ) 图 标 。 键 入 脚 本 , 如 列 表 13-2 所 示 , 接 着 , 把 该 文 件 以 Replace.dsm 为 名存 在 Common\MsDev98\Macros 文 件 夹 中 。Developer Studio 仍 不 能 识 别 这 个 新 的宏 文 件 , 因 此 , 必 须 按 前 一 段 所 说 的 那 样 , 打 开 它 在 Customize 对 话 框 中 的 复 选框 。
配 套 光 盘 的 Chapter.13 文 件 夹 包 括 了 名 为 Column.txt 的 文 本 文 件 , 为 演 示ColumnarReplace 宏 提 供 了 简 单 的 测 试 基 础 ( 你 可 以 使 用 所 要 的 任 何 文 本 文 档 来试 验 )。 为 了 改 变 文 本 中 的 一 栏 , 拖 动 鼠 标 时 按 着 A LT 键 , 先 选 择 一 块 文 本 , 如图 13-11 所 示 。 通 常 , 可 以 从 右 下 角 向 左 上 角 拖 动 鼠 标 来 标 记 一 块 , 但 是 , ColumnarReplace 宏 却 假 定 鼠 标 光 标 从 左 上 方 向 右 下 方 拖 动 。
图 13-11 标记文本块
接 下 来 , 单 击 Tools 菜 单 中 的 Macro 命 令 , 如 果 有 必 要 的 话 , 从 M acro File 下 拉列 表 中 选 择 Replace , 并 双 击 框 中 的 ColumnarReplace,如图 13-10 所 示 。 当 这 个
宏 运 行 时 , 它 查 询 搜 索 的 字 符 串 和 替 换 的 字 符 串 。 并 在 第 一 次 查 询 时 键 入 Word
一 词 :
在 第 二 个 询 问 下 键 入 替 换 文 本 , 如 New :
在 第 二 个 询 问 下 单 击 Enter 键 或 者 单 击 O K , 在 选 择 块 中 , 宏 就 会 代 替 所 有 的“ word ” , 其 余 的 “ word ” 词 不 变 :
把 一 个 敲 键 组 合 指 定 给 宏 ,以 便 可 以 快 速 访 问 它 。在 Customize 对 话 框 的 Keyboard 选 项 卡 中 , 从 Category 框 选 M acro s, 并 从 Commands 表 中 选 ColumnarReplace. 单 击 标 有 Press New Shortcut Ke y( 按 新 的 快 捷 键 )的 框 ,并 键 入 组 合 键 ,如 Alt+R
( 用 于 替 换 ), 能 更 好 地 提 醒 你 宏 的 用 途 。 你 可 以 给 工 具 栏 或 菜 单 增 加 宏 命 令 , 这 与 增 加 Visual C++ 其 他 的 命 令 一 样 。 在 Customize 对 话 框 的 Command 选 项 卡中 , 从 Category 列 表 中 选 择 M acro s, 然 后 , 将 宏 名 称 从 显 示 的 列 表 中 拖 到 工 具栏 或 菜 单 上 , 如 本 章 前 部 分 所 述 。
列 表 13-2 列 出 了 ColumnarReplace 宏 脚 本 。 对 于 脚 本 中 使 用 的 VBScript 元 素 的解 释 , 可 参 考 附 录 C 中 的 教 程 。 此 列 表 后 面 的 简 短 讨 论 , 概 括 了 重 要 的 代 码 段 ,
并 描 述 了 宏 是 如 何 工 作 的 。
列 表 13-2 ColumnarReplace 宏 脚 本
Macro : Columna r search-and-replac e
x2 = ActiveDocument.Selection.Current Colun
x1 = Int( x2 - InStrB(ActiveDocument.Selection,vbCR)/2 + .5)
y1=ActiveDocument Selection TopLine
Prompt for search/replace strings
strFind = InputBox( Enter word to search for, strTitle)
If strFind = "Then Exit Sub"
strReplace = InputBox("Replaece with what word?, strTitle )
Temporarily add a copy of the search string to the end of
the line. This prevents ReplaceText from cycling to the
top of the document after the final replacement.
ActiveDocument.Selection.EndOfLine
ActiveDocument.Selection = ActiveDocument.Selection + strFind
Start from top line of selection and work down
Do While y1 <= y2
ActiveDocument.Selection.GoToLine y1
ActiveDocument.Selection.StartOfLine
Do While ActiveDocument.Selection.CurrentColumn < x1
ActiveDoCument Selection
CharRight dSMove,1
Loop
Do While ActiveDocument.Selection.CurrentColumn < x2
ActiveDocument.Selection.CharRight dsExtend, 1
Loop
ActiveDocument.Selection.ReplaceText strFind, strReplace
y1 = y1 + 1
Loop
Remove temporary string at end of line
ActiveDocument.Selection.EndOfLine
for i= 1 To Len( strFind)
ActiveDocument.Selection.BackSpace
Next
En d Su b
ActiveDocument.Selection 是 VBScript 属 性 ,它 包 含 当 前 文 档 所 选 的 所 有 文 本(“ 属性 ” 是 Visual Basic 术 语 , 指 的 是 一 个 对 象 的 值 , 用 在 这 里 与 第 8 章 中 的 意 思 相同 。 你 还 会 记 得 , 向 上 追 溯 , ActiveX 控 件 来 源 于 Visual Basic 自 定 义 控 件 )。 如果 在 文 档 中 没 有 选 择 文 本 , 则 ActiveDocument.Selection 中 是 空 字 符 串 。ColumnarReplace 通 过 检 查 ActiveDocument.Selection 的 内 容 , 首 先 确 定 在 文 档 中已 选 取 了 文 本 。 如 果 这 个 字 符 串 是 空 的 , 则 宏 终 止 , 而 不 会 采 取 任 何 动 作 。
紧 接 着 , 宏 确 定 选 择 块 的 坐 标 , 坐 标 指 的 是 选 择 区 的 左 上 角 和 左 下 角 的 行 列 位置 。 属 性 ActiveDocument.Selection.CurrentLine 和ActiveDocument.Selection.Current Column 提 供 了 插 入 符 位 置 的 行 和 列 值 , 其 坐 标是 选 择 块 右 下 角 的 坐 标 ( x2, y2 )。 这 便 解 释 了 宏 为 什 么 需 要 你 向 下 拖 动 鼠 标 光标 , 而 不 是 向 上 拖 动 , 来 选 择 文 本 块 。 在 宏 开 始 时 , 插 入 符 必 须 停 在 所 选 块 的 右下 角 , ActiveDocument.Selection.Topline 提 供 了 所 选 块 的 第 一 行 , 并 保 存 在 y1 坐标 中 。 对 于 文 本 块 开 始 的 栏 , 没 有 对 应 的 属 性 。 因 此 , 宏 依 赖 于 这 样 一 个 事 实 , 即 Developer Studio 插 入 一 个 返 回 字 符 , 来 标 出 选 择 栏 中 每 行 的 结 束 位 置 , 选 择栏 包 含 在 ActiveDocument.Selection 中 。 通 过 定 位 字 符 串 的 第 一 个 返 回 字 符 ( 用vbCR 常 量 表 示 ), 宏 确 定 屏 上 列 区 域 块 的 宽 度 , 然 后 , 用 x2 减 去 区 域 块 宽 , 算出 区 域 块 中 的 第 一 个 列 标 x 1 。
单 独 调 用 Input Box 库 函 数 , 将 向 用 户 查 询 搜 索 字 符 串 及 其 替 换 字 符 串 。 如 果 用
户 单 击 Cancel 按 钮 , 或 不 指 定 搜 索 字 符 串 , 宏 将 停 止 。 对 于 替 换 字 符 串 没 有 类似 的 操 作 , 因 为 宏 允 许 用 空 值 作 为 有 效 的 替 换 项 。 如 果 替 换 字 符 串 为 空 , 宏 只 是用 空 来 替 换 搜 索 字 符 串 , 也 就 是 从 文 本 中 删 掉 字 符 串 。 在 InputBox 函 数 中 , 没有 提 供 区 分 使 用 空 替 换 字 符 串 单 击 OK 与 单 击 Cancel 之 间 的 手 段 。 因 此 , 这 个宏 对 于 这 两 个 操 作 的 处 理 是 一 样 的 。
Developer Studio 提 供 取 消 运 行 宏 的 手 段 , 当 宏 运 行 时 , Developer Studio 宏 图 标显 示 在 W indows 任 务 栏 右 边 的 系 统 区 中 :
双 击 这 个 图 标 , 显 示 结 束 宏 的 确 认 消 息 。 因 为 在 确 认 消 息 显 示 时 , 宏 继 续 运 行 , 所 以 , 必 须 要 尽 快 对 此 消 息 作 出 反 应 。
ColumnarReplace 中 主 循 环 的 每 一 次 重 复 , 都 能 替 换 被 选 区 的 每 一 行 文 本 。 循 环从 区 域 顶 端 行 标 ( y1 ) 开 始 , 一 次 下 移 一 行 , 直 到 到 达 区 域 底 部 的 行 标 ( y2 ) 处 :
Do e y1 <= y2
ActiveDocument.Selection.ReplaceText strFind, strReplace y1 = y1 + 1
Loop
第 一 个 嵌 套 循 环 把 插 入 符 从 行 的 始 端 移 到 区 域 块 的 第 一 列 :
Find left edge of selected column
Do While ActiveDocument. Selection.CurrentColumn < x1 ActiveDocument.Selection.CharRight dsMove, 1
Loop
嵌 套 的 第 二 个 循 环 移 动 插 入 符 ,并 一 次 通 过 块 中 的 一 个 字 符 ,并 在 运 行 时 选 择 文本 :
Select text across width of column
Do While ActiveDocument.Selection. CurrentColumn < x2
ActiveDocument.Selection.CharRight dsExtend, 1
Loop
如 下 显 示 的 是 选 择 的 文 本 覆 盖 原 来 区 域 块 的 结 果 :
一 旦 在 一 行 中 已 经 选 择 了 一 部 分 文 本 , 宏 就 调 用 ReplaceText。 这 个 Developer Studio 方 式 使 用 替 换 字 符 串 替 换 每 个 StrFind 搜 索 字 符 串 。 这 种 搜 索 不 区 分 大 小写 ,尽 管 ColumnarReplace 能 轻 而 易 举 地 进 行 修 改 ,成 为 可 以 区 分 大 小 写 的 方 法 。ReplaceText 只 作 用 于 所 选 文 本 , 不 是 在 整 行 上 , 这 是 整 个 宏 的 关 键 。 当 主 循 环重 复 时 , 对 于 所 选 块 最 后 一 行 的 下 一 行 , 这 一 过 程 将 重 复 进 行 。
Developer Studio 附 加 项
通 过 创 建 附 加 项 , 有 助 于 增 强 Developer Studio 的 环 境 , 附 加 项 是 ActiveX 动 态链 接 库 , 这 是 Visual C++ 在 启 动 时 加 载 的 , 并 可 以 响 应 用 户 的 命 令 。 附 加 项 可 以为 环 境 提 供 集 成 的 特 征 , 但 不 能 与 宏 相 结 合 。 由 于 它 们 可 以 访 问 全 部 W indows AP I , 附 加 项 能 完 成 以 下 任 务 , 诸 如 文 件 的 输 入 与 输 出 、 通 信 、 打 印 、 Internet 支 持 等 所 有 想 让 程 序 做 的 工 作 。 在 使 用 InputBox 和 MsgBox 函 数 显 示 的 对 话 框中 , 宏 能 与 用 户 联 系 , 附 加 项 包 含 有 自 己 的 资 源 数 据 , 也 能 显 示 对 话 框 和 自 己 设计 的 属 性 表 。 附 加 项 的 不 利 之 处 在 于 创 建 时 颇 费 时 间 和 精 力 。
本 节 只 粗 略 地 描 述 了 在 Developer Studio 中 附 加 项 是 怎 样 工 作 和 开 始 编 写 的 , 但由 于 有 关 附 加 项 的 主 题 相 当 深 广 , 其 中 的 许 多 对 象 、 属 性 、 方 法 和 事 件 都 必 须 进行 说 明 。所 以 ,详 细 讨 论 这 个 主 题 超 出 了 本 书 的 范 围 。更 详 细 消 息 ,可 参 阅 Visual C++ 的 在 线 帮 助 ,或 研 究 Visual C++ 提 供 的 Api2Help 项 目 的 源 代 码 。为 了 在 硬 盘上 复 制 Api2Help 项 目 , 应 当 按 题 为 Copying the Sample Add-ins( 复 制 样 本 附 加项 ) 的 在 线 论 文 中 所 概 括 的 步 骤 , 它 的 位 置 在 索 引 项 sample add-in s( 样 本 附 加项 ) 下 面 。
要 开 始 一 个 附 加 项 项 目 , 从 File ( 文 件 ) 菜 单 中 选 择 New 命 令 , 并 单 击 Projects 选 项 卡 。 输 入 一 个 项 目 名 , 并 双 击 DevStudio Add-in Wizard 图 标 , 来 启 动 创 建 项目 的 向 导 , 这 个 向 导 用 一 步 即 可 创 建 项 目 :
Add-in Wizard 会 自 动 设 置 项 目 , 并 生 成 源 代 码 , 对 于 大 多 数 编 写 附 加 项 的 工 作 , 这 些 源 代 码 依 赖 于 MFC 和 ATL 。对 于 不 需 要 MFC 的 附 加 项 项 目 ,需 用 ATL COM AppWizard 来 开 始 项 目 , 而 不 是 DevStudio Add-in Wizard 。 从 Insert 菜 单 中 选 New ATL Object ( 新 的 ATL 目 标 ) 命 令 , 双 击 ATL Object Wizard 对 话 框 中 的 Add-in
Object 图 标 。用 C 或 Visual Basic 也 可 写 附 加 项 , 但 这 样 的 话 , W izards 所 带 来 的便 利 和 优 势 就 都 不 能 用 了 。
在 编 码 和 建 立 附 加 项 的 动 态 链 接 库 后 , 紧 接 着 , 把 DLL 文 件 的 信 息 通 知 给Developer Studio , 以 便 加 载 库 , 并 调 用 它 。调 用 Customize 对 话 框 ,单击 Add-Ins And Macro Files ( 附 加 项 和 宏 文 件 ) 选 项 卡 , 在 浏 览 了 DLL 文 件 后 , 双 击 表 中的 该 文 件 。 也 可 将 该 文 件 复 制 到 Common/MsDev98/Add-in 文 件 夹 中 , 这 就 没 有必 要 再 浏 览 了 。在 当 前 的 Developer Studio 会 话 中 加 载 附 加 项 库 ,打开 Customize 对 话 框 中 用 于 附 加 项 文 件 的 复 选 框 。这 个 复 选 框 处 于 打 开 状 态 会 使 Visual C++ 自动 将 动 态 链 接 库 文 件 在 启 动 时 自 动 加 载 。
图 13-12 说 明 了 附 加 项 和 Developer Studio 是 如 何 相 互 作 用 的 。 一 个 附 加 项 将 两个
对 象 展 现 给 Developer Studio , 并 命 名 为 Commands 和 DSAddIn , Add-in Wizard 在 Commands.cpp 和 DSAddIn,cpp 文 件 中 创 建 类 的 源 代 码 。 Commands 对 象 则 容纳 着 完 成 附 加 项 提 供 的 全 部 命 令 的 方 法 , 而 DSAddIn 对 象 仅 容 纳 名 为OnConnection 和 OnDisconnection 的 两 种 方 法 。 当 加 载 ( 或 连 接 ) 附 加 项 时 , Developer Studio 调 用 第 一 种 方 法 。 在 卸 载 附 加 项 时 , 则 需 要 调 用 第 二 种 方 法 。附 加 项 开 始 执 行 时 , OnConnection 方 法 获 得 控 件 , 并 调 用 Developer Studios 的AddCommand 函 数 。 调 用 AddCommand 给 环 境 增 加 新 的 命 令 , 并 提 供 Developer
Studio 需 要 呈 现 给 用 户 的 所 有 命 令 信 息 。 新 的 命 令 通 过 动 态 链 接 库 来 提 供 服 务 , 但 对 用 户 来 说 , 这 个 命 令 与 Developer Studio 内 部 命 令 集 中 的 任 何 命 令 一 样 。AddCommand 的 参 数 可 以 用 来 指 定 信 息 , 例 如 命 令 名 称 , 在 菜 单 上 选 择 命 令 后 , Developer Studio 的 状 态 栏 显 示 的 文 本 、 工 具 提 示 的 文 本 和 命 令 的 工 具 栏 按 钮 , 以 及 通 过 附 加 项 动 态 链 接 库 导 出 的 方 法 名 称 ,在 用 户 调 用 这 个 命 令 时 ,Developer Studio 应 当 调 用 该 动 态 链 接 库 。
Developer Studio 也 能 导 出 两 种 其 他 方 法 , 使 得 附 加 项 在 用 户 使 用 时 可 以 更 快 地访 问 命 令 。 AddCommandBarButton 可 指 示 Developer Studio 为 附 加 项 导 出 的 命 令创 建 一 个 工 具 栏 按 钮 , AddKeyBinding 则 可 为 命 令 设 置 组 合 键 。 尽 管 将 敲 键 存 入系 统 中 , 可 以 节 省 用 户 指 定 键 的 麻 烦 , 我 们 却 不 推 荐 调 用 AddKeyBinding , 因 为它 有 可 能 会 覆 盖 现 存 的 敲 键 , 给 用 户 引 起 混 淆 。 通 常 , 在 附 加 项 开 始 后 , 最 好 是使 附 加 项 命 令 成 为 未 结 合 的 命 令 , 让 用 户 在 Customize 对 话 框 中 设 置 一 个 键 。 对当 前 键 组 合 的 列 表 来 说 , 附 加 项 无 法 查 询 Developer Studio 。
图 13-12 Developer Studio 怎样与附加的动态链接库联系
Developer Studio 提 供 了 一 系 列 代 表 环 境 方 面 的 对 象 , 例 如 建 立 和 配 置 信 息 , 打开 文 档 、 调 试 器 、 窗 口 等 等( 附 录 C 解 释 了 Visual C++ 宏 怎 样 使 用 这 些 对 象 的 )。通 过 一 个 对 象 的 属 性 和 方 法 , 附 加 项 能 获 得 或 设 置 与 环 境 相 关 的 详 细 信 息 。 例
如 , 在 表 13-4 和 表 13-5 中 , 列 出 了 主 要 的 Developer Studio 对 象 ( 名 为Application ) 所 包 含 的 所 有 属 性 和 方 法 的 集 合 。 在 简 单 的 宏 脚 本 中 显 示 出 来 , 可获 得 熟 悉 的 属 性 字 符 串 和 其 他 值 。 例 如 在 一 个 宏 中 执 行 此 行 :
MsgBox(Application.Name+"version"+"Application.Version)
产 生 如 下 消 息 :
Application 和 Debugger 是 启 动 事 件 的 唯 一 Developer Studio 对 象 , 且 Debugger 仅 能 启 动 BreakPointHit 事 件 , 它 通 知 附 加 项 , 在 调 试 器 已 经 触 发 断 点 ( 见 第 11 章 的 “ 断 点 ” 部 分 , 该 节 专 门 论 述 了 调 试 器 的 断 点 )。 Application 对 象 能 启 动 12 种 事 件 , 本 书 将 它 们 列 在 表 13-6 中 , Add-in Wizard ( 附 加 项 向 导 ) 产 生 的CCommands 类 包 含 所 有 启 动 事 件 的 外 壳 处 理 程 序 函 数 , 要 使 附 加 项 得 知Developer Studio 的 当 前 状 况 ,可 增 加 实 现 代 码 ,以便在 Commands.cpp 文 件 中 选
择 事 件 处 理 函 数 。
表 13-4 Application 对 象 的 属 性
属 性 说 明
Active
ActiveConfigurati on
ActiveDocument ActiveProject ActiveWindow CurrentDirectory Debugger Documents FullName
用来指示是否 Developer Studio 处于运行状态的布尔值
含有当前项目配置的字符串,通常是 Win32 Release 或 Win32 Debug
活动文档窗口名当前项目名
当前活动的 Developer Studio 窗口标题
Open 命令使用的当前目录
代表 Visual C++ 调试器的对象代表打开文档的对象
Developer Studio 可执行的路径和文件名,通常是 C:\Program Files\MicrosoftVisual Studio\Common\MsDev98\Bin\MsDev.exe
Height
Left Name Parent Path
Projects TextEditor Top VersionVisible Width
含有 Developer Studio 主窗口高度的 long 型值,以像素为单位含有主窗口左端的 long 型值
含有 Microsoft Developer Studio 文本的字符串
Application 的父对象
Developer Studio 的 可 执 行 文 件 路 径 , 通 常 是
C:\ProgramFiles\Microsoft isual Studio\Common\MsDev98\Bin
当前工作空间中代表所有项目的集合对象代表 Visual C++ 文本编辑器的对象
含有 Developer Studio 主窗口顶端边界的 y 坐标 long 型值
含 有 当 前 Developer Studio 版 本 ( 如 6.0 ) 的 字 符 串 决 定
Developer Studio 主窗口是否可视的 Boolean 值
Windows WindowsState
含有 Developer Studio 主窗口宽度的 long 型值代表所有打开窗口的集合对象
代 表 主 窗 口 状 态 的 long 型 值 。 值 可 能 包 括dsWindowStateMaximized 和 DswindowStateMinimized 常量,用来指示 Developer Studio 是最大还是最小
表 13-5 Application 对 象 的 方 法
方 法 说 明
AddCommand AddCommandBarButton AddKeybinding
Build EnableModeless ExecuteCommand ExecuteConfiguration GetPackageExtension
对 Visual C++ 添加通过附加项定义的命令为附加项创建一个工具栏
给附加项命令指定一个键组合
通过只处理变化的文件来创建项目
在 Developer Studio 中使模态窗口有效或失效执行一条指定的命令或 VBScript 宏
运行通过项目创建的程序
对 Developer Studio 之外的其他对象提供访问
续表
PrintToOutputWindow
Quit RebuildAll SetAddInInfo
将一个字符串写到 Output(输出)窗口之外的 Macro 选项卡中 (可参见附录 C 的例子)
提示用户保存文档 ,关闭所有的文档窗口 ,并关闭 Developer Studio
执行 Developer Studio Rebuild All 命令提供关于附加项的信息
表 13-6 Application 对 象 的 事 件
应 用 程 序 事 件 使 用 时 间
BeforeApplicationShutDo wn
BeforeBuildStart BeforeDocumentClose BuildFinish DocumentOpen DocumentSave
在 Developer Studio 关闭之前
用户选择 Developer Studio Build 命令之后 ,但在编辑开始之前在一个文档关闭之前,当事件发生时,文档仍打开着
当创建完成时 (成功或者不成功)
在一个文档打开之后
在一个文档保存之后,当事件发生时,老文档已经被覆盖
NewDocument NewWorkSpace WindowActive
当一个新文档被创建时,当事件发生时,文档处于打开状态
当一个新的工作空间被创建时
当一个窗口处于活动状态时 。此事件用在编辑器里的文档窗口
续表
WindowDeactivate WorkspaceClose WorkspaceOpen
和 Developer Studio 应用程序窗口里,例如调试器窗口在一个窗口失效或被关闭之后
在一个工作空间被关闭之后在一个工作空间被打开以后