Clang 编译器用户手册

简介

Clang 编译器是一个用于 C 语言家族的开源编译器,旨在成为这些语言的最佳实现。Clang 基于 LLVM 优化器和代码生成器,使其能够为许多目标提供高质量的优化和代码生成支持。有关更多一般信息,请参阅 Clang 网站LLVM 网站

本文档描述了将 Clang 用作编译器的最终用户需要了解的重要事项,包括支持的特性、命令行选项等。如果您有兴趣使用 Clang 构建处理代码的工具,请参阅 “Clang” CFE 内部手册。如果您有兴趣了解 Clang 静态分析器,请参阅其网页。

Clang 是 C 语言家族完整工具链中的一个组件。另一份文档描述了 构建完整工具链 所需的其他部分。

Clang 旨在支持 C 语言家族,包括 CObjective-CC++Objective-C++ 以及这些语言的许多方言。有关特定语言的信息,请参阅相应的语言特定部分

除了这些基础语言及其方言,Clang 还支持各种语言扩展,这些扩展在相应的语言部分有记录。提供这些扩展是为了与 GCC、Microsoft 和其他流行编译器兼容,并通过 Clang 特定功能来改进功能。Clang 驱动程序和语言特性在设计上有意与 GNU GCC 编译器尽可能兼容,从而简化从 GCC 迁移到 Clang。在大多数情况下,代码“按预期运行”。Clang 还提供了一个备用驱动程序 clang-cl,该驱动程序旨在与 Visual C++ 编译器 cl.exe 兼容。

除了特定语言的特性之外,Clang 还具有一些取决于正在编译的 CPU 架构或操作系统的特性。有关更多详细信息,请参阅 目标特定特性和限制 部分。

本简介的其余部分介绍了一些基本 编译器术语,这些术语将在本手册中使用,并且包含一个基本的 Clang 用法介绍,作为命令行编译器。

术语

前端、解析器、后端、预处理器、未定义行为、诊断信息、优化器

基本用法

为新手介绍如何使用 C 编译器。

编译 + 链接 编译然后链接 调试信息 启用优化 选择要使用的语言,默认情况下为 C17。根据扩展自动识别。使用 makefile

命令行选项

本节通常是其他节的索引。它不会深入探讨其他节中涵盖的内容。但是,第一部分介绍了语言选择和其他高级选项,例如 -c-g 等。

控制错误和警告消息的选项

-Werror

将警告转换为错误。

-Werror=foo

将警告“foo”转换为错误。

-Wno-error=foo

即使指定了 -Werror,也将警告“foo”转换为警告。

-Wfoo

启用警告“foo”。有关可以以这种方式指定的所有警告标志的完整列表,请参阅 诊断信息参考

-Wno-foo

禁用警告“foo”。

-w

禁用所有诊断信息。

-Weverything

启用所有诊断信息。

-pedantic

对语言扩展发出警告。

-pedantic-errors

对语言扩展发出错误。

-Wsystem-headers

启用来自系统头文件的警告。

-ferror-limit=123

在生成 123 个错误后停止发出诊断信息。默认值为 20,可以使用 -ferror-limit=0 禁用错误限制。

-ftemplate-backtrace-limit=123

仅在单个警告或错误的模板实例化回溯中发出不超过 123 个模板实例化注释。默认值为 10,可以使用 -ftemplate-backtrace-limit=0 禁用限制。

诊断信息格式

Clang 默认旨在生成美观的诊断信息,特别是针对第一次使用 Clang 的新用户。但是,不同的人有不同的偏好,有时 Clang 不是由人驱动的,而是由需要一致且易于解析输出的程序驱动的。对于这些情况,Clang 提供了多种选项来控制其生成的诊断信息的精确输出格式。

-f[no-]show-column

在诊断信息中打印列号。

此选项默认为开启,控制 Clang 是否打印诊断信息的列号。例如,当启用此选项时,Clang 将打印类似以下内容:

test.c:28:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
#endif bad
       ^
       //

当禁用此选项时,Clang 将打印“test.c:28: warning…”,不带列号。

打印的列号从行首开始计算字节数;如果您的源代码包含多字节字符,请注意。

-f[no-]show-source-location

在诊断信息中打印源文件/行/列信息。

此选项默认为开启,控制 Clang 是否打印诊断信息的文件名、行号和列号。例如,当启用此选项时,Clang 将打印类似以下内容:

test.c:28:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
#endif bad
       ^
       //

当禁用此选项时,Clang 将不会打印“test.c:28:8: ”部分。

-f[no-]caret-diagnostics

在诊断信息中打印源代码行和范围。此选项默认为开启,控制 Clang 在发出诊断信息时是否打印源代码行、源代码范围和插入符号。例如,当启用此选项时,Clang 将打印类似以下内容:

test.c:28:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
#endif bad
       ^
       //
-f[no-]color-diagnostics

此选项默认为开启,当检测到支持颜色的终端时,控制 Clang 是否以彩色打印诊断信息。

当启用此选项时,Clang 将使用颜色突出显示诊断信息的特定部分,例如:

  test.c:28:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
  #endif bad
         ^
         //

当禁用此选项时,Clang 将只打印

test.c:2:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
#endif bad
       ^
       //

如果定义了 NO_COLOR 环境变量,且不为空(无论值如何),则禁用彩色诊断信息。如果定义了 NO_COLOR 并且在命令行中传递了 -fcolor-diagnostics,Clang 将遵循命令行参数。

-fansi-escape-codes

控制是否使用 ANSI 转义码而不是 Windows 控制台 API 来输出彩色诊断信息。此选项仅在 Windows 上使用,默认情况下为关闭。

-fdiagnostics-format=clang/msvc/vi

更改诊断信息输出格式,以更好地匹配 IDE 和命令行工具。

此选项控制诊断消息中打印的文件名、行号和列号的输出格式。选项及其对简单转换诊断格式的影响如下

clang (默认)
t.c:3:11: warning: conversion specifies type 'char *' but the argument has type 'int'
msvc
t.c(3,11) : warning: conversion specifies type 'char *' but the argument has type 'int'
vi
t.c +3:11: warning: conversion specifies type 'char *' but the argument has type 'int'
-f[no-]diagnostics-show-option

在诊断信息行中启用 [-Woption] 信息。

此选项默认为开启,控制 Clang 在输出警告诊断信息时是否打印关联的 警告组 选项名称。例如,在此输出中

test.c:28:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
#endif bad
       ^
       //

传递 -fno-diagnostics-show-option 将阻止 Clang 在诊断信息中打印 [-Wextra-tokens] 信息。此信息告诉您用于启用或禁用诊断信息的标志,无论是从命令行还是通过 #pragma GCC diagnostic

-fdiagnostics-show-category=none/id/name

启用在诊断信息行中打印类别信息。

此选项默认为“none”,控制 Clang 在发出诊断信息时是否打印与诊断信息关联的类别。每个诊断信息可能或可能没有关联的类别,如果有,它将列在诊断信息行的诊断信息分类字段中(在 [] 中)。

例如,格式字符串警告将根据此选项的设置产生以下三种形式

t.c:3:11: warning: conversion specifies type 'char *' but the argument has type 'int' [-Wformat]
t.c:3:11: warning: conversion specifies type 'char *' but the argument has type 'int' [-Wformat,1]
t.c:3:11: warning: conversion specifies type 'char *' but the argument has type 'int' [-Wformat,Format String]

此类别可供想要按类别对诊断信息进行分组的客户端使用,因此它应该是一个高级别类别。我们希望有数十个此类类别,而不是数百个或数千个类别。

-f[no-]save-optimization-record[=<format>]

在编译期间启用优化备注,并将它们写入单独的文件。

此选项默认为关闭,控制 Clang 是否将优化报告写入单独的文件。通过将诊断信息记录到文件中,用户可以以便捷的方式解析或排序备注。

默认情况下,序列化格式为 YAML。

支持的序列化格式为

  • -fsave-optimization-record=yaml: 结构化的 YAML 格式。

  • -fsave-optimization-record=bitstream: 基于 LLVM 位流的二进制格式。

输出文件由 -foptimization-record-file 控制。

在没有显式输出文件的情况下,文件将使用以下方案选择

<base>.opt.<format>

其中 <base> 基于与 -c-S 一起使用时编译的输出文件(无论是否通过 -o 显式指定)。例如

  • clang -fsave-optimization-record -c in.c -o out.o 将生成 out.opt.yaml

  • clang -fsave-optimization-record -c in.c 将生成 in.opt.yaml

当针对 (Thin)LTO 时,基础部分来自输出文件名,并且扩展名不会被删除。

当针对 ThinLTO 时,将使用以下方案

<base>.opt.<format>.thin.<num>.<format>

仅限 Darwin:当用于从源文件生成链接的二进制文件(通过中间目标文件)时,驱动程序将调用 cc1 生成临时目标文件。临时备注文件将与目标文件一起发出,然后将被 dsymutil 拾取并在 .dSYM 包中发出。这适用于除 YAML 之外的所有格式。

例如

clang -fsave-optimization-record=bitstream in.c -o out 将生成

  • /var/folders/43/9y164hh52tv_2nrdxrj31nyw0000gn/T/a-9be59b.o

  • /var/folders/43/9y164hh52tv_2nrdxrj31nyw0000gn/T/a-9be59b.opt.bitstream

  • out

  • out.dSYM/Contents/Resources/Remarks/out

仅限 Darwin:为多个体系结构编译将使用以下方案

<base>-<arch>.opt.<format>

请注意,这与传递 -foptimization-record-file 选项不兼容。

-foptimization-record-file

控制写入优化报告的文件。这意味着 -fsave-optimization-record.

在 Darwin 平台上,这与传递多个 -arch <arch> 选项不兼容。

-foptimization-record-passes

仅包含与指定正则表达式匹配的传递。

当输出优化报告时(参见 -fsave-optimization-record),此选项控制将包含在最终报告中的传递。

如果未使用此选项,则优化记录中将包含所有传递。

-f[no-]diagnostics-show-hotness

在诊断信息行中启用配置文件热点信息。

此选项控制在存在配置文件引导优化信息的情况下,Clang 是否打印与诊断信息关联的配置文件热点。目前,这在优化备注中受支持(参见 用于发出优化报告的选项)。热点信息使用户可以关注与运行时性能最相关的热点优化备注。

例如,在此输出中,包含 foo 调用站点的代码块根据配置文件数据执行了 3000 次

s.c:7:10: remark: foo inlined into bar (hotness: 3000) [-Rpass-analysis=inline]
  sum += foo(x, x - 2);
         ^

当使用 -fsave-optimization-record 时,此选项将隐式启用。否则,它默认为关闭。

-fdiagnostics-hotness-threshold

如果优化备注的热点值至少不等于此值,则阻止输出优化备注。

此选项默认为零,控制 Clang 输出优化备注所需的最小热点值。目前,当启用诊断信息中的配置文件热点信息时,这在优化备注中受支持(参见 用于发出优化报告的选项)(参见 -fdiagnostics-show-hotness)。

-f[no-]diagnostics-fixit-info

在诊断信息输出中启用“FixIt”信息。

此选项默认为开启,控制 Clang 在知道如何修复特定诊断信息时是否打印修复信息。例如,在此输出中

test.c:28:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
#endif bad
       ^
       //

传递 -fno-diagnostics-fixit-info 将阻止 Clang 打印消息末尾的“//”行。此信息对于可能不理解错误的用户很有用,但对于机器解析来说可能会令人困惑。

-fdiagnostics-print-source-range-info

打印有关源代码范围的机器可解析信息。此选项使 Clang 在文件/行/列号信息之后以机器可解析的格式打印有关源代码范围的信息。该信息是一个简单的花括号括起来的范围序列,每个范围都列出了开始和结束行/列位置。例如,在此输出中

exprs.c:47:15:{47:8-47:14}{47:17-47:24}: error: invalid operands to binary expression ('int *' and '_Complex float')
   P = (P-42) + Gamma*4;
       ~~~~~~ ^ ~~~~~~~

{} 由 -fdiagnostics-print-source-range-info 生成。

打印的列号从行首开始计算字节数;如果您的源代码包含多字节字符,请注意。

-fdiagnostics-parseable-fixits

以机器可解析的格式打印修复建议。

此选项使 Clang 在诊断信息的末尾以机器可解析的格式打印可用的修复建议。以下示例说明了此格式

fix-it:"t.cpp":{7:25-7:29}:"Gamma"

打印的范围是半开范围,因此在此示例中,应将 t.cpp 文件第 7 行第 25 列到第 29 列(不包括第 29 列)的字符替换为字符串“Gamma”。范围或替换字符串都可以为空(分别表示严格插入和严格删除)。文件名和插入字符串都对反斜杠(为“\\”)、制表符(为“\t”)、换行符(为“\n”)、双引号(为“\””)和不可打印字符(为八进制“\xxx”)进行转义。

打印的列号从行首开始计算字节数;如果您的源代码包含多字节字符,请注意。

-fno-elide-type

关闭模板类型打印中的省略。

模板类型打印的默认行为是尽可能省略模板参数,删除两个模板类型中相同的参数,只保留差异。添加此标志将打印所有模板参数。如果终端支持,则不同的参数仍将突出显示。

默认值

t.cc:4:5: note: candidate function not viable: no known conversion from 'vector<map<[...], map<float, [...]>>>' to 'vector<map<[...], map<double, [...]>>>' for 1st argument;

-fno-elide-type

t.cc:4:5: note: candidate function not viable: no known conversion from 'vector<map<int, map<float, int>>>' to 'vector<map<int, map<double, int>>>' for 1st argument;
-fdiagnostics-show-template-tree

模板类型差异打印文本树。

对于差异较大的模板化类型,此选项将使 Clang 将模板显示为缩进的文本树,每行一个参数,并以内联方式标记差异。这与 -fno-elide-type 兼容。

默认值

t.cc:4:5: note: candidate function not viable: no known conversion from 'vector<map<[...], map<float, [...]>>>' to 'vector<map<[...], map<double, [...]>>>' for 1st argument;

使用 -fdiagnostics-show-template-tree

t.cc:4:5: note: candidate function not viable: no known conversion for 1st argument;
  vector<
    map<
      [...],
      map<
        [float != double],
        [...]>>>
-fcaret-diagnostics-max-lines:

控制 Clang 为诊断信息打印多少行代码。默认情况下,Clang 打印最多 16 行代码。

-fdiagnostics-show-line-numbers:

控制 Clang 是否为它为诊断信息打印的每一行代码在左侧打印包含行号的边距。

默认值

test.cpp:5:1: error: 'main' must return 'int'
    5 | void main() {}
      | ^~~~
      | int

使用 -fno-diagnostics-show-line-numbers

test.cpp:5:1: error: 'main' must return 'int'
void main() {}
^~~~
int

单个警告组

待办事项:从 tblgen 生成此项。为每个警告组定义一个锚点。

-Wextra-tokens

警告预处理指令末尾的冗余标记。

此选项默认情况下处于开启状态,它会启用关于预处理指令末尾冗余标记的警告。例如

test.c:28:8: warning: extra tokens at end of #endif directive [-Wextra-tokens]
#endif bad
       ^

这些冗余标记不是严格符合标准的,通常最好通过注释掉它们来处理。

-Wambiguous-member-template

警告使用未限定的成员模板,其名称解析为使用位置处的另一个模板。

此选项默认情况下处于开启状态,它会在以下代码中启用警告

template<typename T> struct set{};
template<typename T> struct trait { typedef const T& type; };
struct Value {
  template<typename T> void set(typename trait<T>::type value) {}
};
void foo() {
  Value v;
  v.set<double>(3.2);
}

C++ [basic.lookup.classref] 要求这应该是一个错误,但由于难以解决,Clang 作为扩展将其降级为警告。

-Wbind-to-temporary-copy

警告在将引用绑定到临时对象时,不可用的复制构造函数。

此选项会启用关于将引用绑定到临时对象时的警告,前提是该临时对象没有可用的复制构造函数。例如

struct NonCopyable {
  NonCopyable();
private:
  NonCopyable(const NonCopyable&);
};
void foo(const NonCopyable&);
void bar() {
  foo(NonCopyable());  // Disallowed in C++98; allowed in C++11.
}
struct NonCopyable2 {
  NonCopyable2();
  NonCopyable2(NonCopyable2&);
};
void foo(const NonCopyable2&);
void bar() {
  foo(NonCopyable2());  // Disallowed in C++98; allowed in C++11.
}

请注意,如果 NonCopyable2::NonCopyable2() 具有默认参数,其实例化产生编译错误,即使此警告被关闭,该错误在 C++98 模式下仍然是一个硬错误。

控制 Clang 崩溃诊断的选项

尽管听起来不可思议,但 Clang 确实会偶尔崩溃。通常情况下,只有那些使用 最新版本的人才会遇到这种情况。Clang 竭尽全力帮助您提交错误报告。具体而言,Clang 在崩溃时会生成预处理后的源文件和关联的运行脚本。这些文件应该附加到错误报告中,以便更容易重现故障。以下是用于控制崩溃诊断的命令行选项。

-fcrash-diagnostics=<val>

有效值为

  • off(在 Clang 崩溃期间禁用自动生成预处理后的源文件。)

  • compiler(生成编译器崩溃的诊断信息(默认))

  • all(为所有支持此功能的工具生成诊断信息)

-fno-crash-diagnostics

在 Clang 崩溃期间禁用自动生成预处理后的源文件。

-fno-crash-diagnostics 标志有助于加快生成增量缩减的测试用例的过程。

-fcrash-diagnostics-dir=<dir>

指定写入崩溃诊断文件的位置;默认为临时文件的默认位置。

CLANG_CRASH_DIAGNOSTICS_DIR=<dir>

-fcrash-diagnostics-dir=<dir> 相似,指定写入崩溃诊断文件的位置,但优先级低于选项。

即使没有崩溃,Clang 也能够生成预处理后的源文件和关联的运行脚本。这在尝试使用模块时为警告或错误生成可重现的错误信息时特别有用。

-gen-reproducer

生成预处理后的源文件、可重现脚本以及(如果有)包含以下内容的缓存:已构建的模块 pcm 和重建相同模块所需的所有头文件。

生成优化报告的选项

优化报告以高级别跟踪编译器转换所做的所有主要决策。例如,当内联器决定将函数 foo() 内联到 bar() 中时,或者循环展开器决定将循环展开 N 次时,或者向量化器决定将循环主体向量化时。

Clang 提供了一系列标志,优化器可以在三种情况下使用这些标志来发出诊断信息

  1. 当传递进行转换时(-Rpass)。

  2. 当传递无法进行转换时(-Rpass-missed)。

  3. 当传递确定是否进行转换时(-Rpass-analysis)。

注意:虽然以下讨论重点关注 -Rpass,但相同的选项也适用于 -Rpass-missed-Rpass-analysis

由于编译器中有数十个传递,因此这些标志中的每一个都采用正则表达式来标识应该发出相关诊断信息的传递的名称。例如,要获取来自内联器的报告,请使用以下命令编译代码

$ clang -O2 -Rpass=inline code.cc -o code
code.cc:4:25: remark: foo inlined into bar [-Rpass=inline]
int bar(int j) { return foo(j, j - 2); }
                        ^

请注意,来自内联器的备注由 [-Rpass=inline] 标识。要请求来自每个优化传递的报告,您应该使用 -Rpass=.*(实际上,您可以使用任何有效的 POSIX 正则表达式)。但是,不要期望来自编译器进行的每个转换的报告。优化备注在主要转换(例如,内联、向量化、循环优化)之外没有意义,并非每个优化传递都支持此功能。

请注意,在使用概要文件引导的优化信息时,概要文件热点信息可以包含在备注中(请参见 -fdiagnostics-show-hotness)。

当前限制

  1. 引用函数名的优化备注将显示函数的经过修饰的名称。由于这些备注是由编译器的后端发出的,因此它不知道任何关于输入语言或其修饰规则的信息。

  2. 一些源代码位置没有正确显示。前端比调试信息中包含的位置具有更详细的源代码位置跟踪(例如,前端可以定位宏展开中的代码)。但是,-Rpass 使用的位置是从调试注释翻译而来。这种翻译可能是丢失的,这会导致一些备注没有位置信息。

生成资源消耗报告的选项

这些选项会报告不同编译步骤的执行时间和消耗的内存。

-fproc-stat-report=

此选项请求驱动程序打印每个编译步骤使用的内存和执行时间。clang 驱动程序在执行过程中会调用不同的工具,例如编译器、汇编器、链接器等。使用此选项,驱动程序会报告每个调用的工具的总执行时间、在用户模式下花费的执行时间以及峰值内存使用情况。

$ clang -fproc-stat-report=abc foo.c
$ cat abc
clang-11,"/tmp/foo-123456.o",92000,84000,87536
ld,"a.out",900,8000,53568

每行上的数据表示

  • 工具可执行文件的名称,

  • 用引号括起来的输出文件名,

  • 总执行时间(微秒),

  • 在用户模式下花费的执行时间(微秒),

  • 峰值内存使用量(Kb)。

可以不带任何值地指定此选项。在这种情况下,统计信息以人类可读的格式打印到标准输出

$ clang -fproc-stat-report foo.c
clang-11: output=/tmp/foo-855a8e.o, total=68.000 ms, user=60.000 ms, mem=86920 Kb
ld: output=a.out, total=8.000 ms, user=4.000 ms, mem=52320 Kb

选项中指定的报告文件被锁定以进行写入,因此此选项可用于在并行构建中收集统计信息。报告文件不会被清除,新数据会追加到其中,因此可以累积构建统计信息。

您也可以使用环境变量来控制进程统计信息的报告。将 CC_PRINT_PROC_STAT 设置为 1 将启用此功能,报告将以人类可读的格式输出到标准输出。将 CC_PRINT_PROC_STAT_FILE 设置为完整限定的路径,使其以 CSV 格式将进程统计信息报告到给定文件。指定相对路径可能会导致在不同目录中创建多个同名文件,因为路径相对于不断变化的工作目录。

当您需要在不更改构建脚本或更改现有编译器选项集的情况下请求统计信息报告时,这些环境变量非常方便。请注意,-fproc-stat-report 的优先级高于 CC_PRINT_PROC_STATCC_PRINT_PROC_STAT_FILE

$ export CC_PRINT_PROC_STAT=1
$ export CC_PRINT_PROC_STAT_FILE=~/project-build-proc-stat.csv
$ make

其他选项

不适合其他类别的 Clang 选项。

-fgnuc-version=

此标志控制 __GNUC__ 和相关宏的值。此标志不会启用或禁用 Clang 中实现的任何 GCC 扩展。将版本设置为零会导致 Clang 将 __GNUC__ 和其他 GNU 命名空间宏(如 __GXX_WEAK__)保留为未定义。

-MV

在发出依赖文件时,使用适合 NMake 或 Jom 的格式约定。除非其他选项导致 Clang 发出依赖文件,否则会被忽略。

当 Clang 发出依赖文件(例如,您提供了 -M 选项)时,大多数文件名可以写入文件而无需任何特殊格式。不同的 Make 工具将不同的字符集视为“特殊”字符,并使用不同的约定来告诉 Make 工具该字符实际上是文件名的一部分。通常,Clang 使用反斜杠来“转义”特殊字符,这是 GNU Make 使用的约定。-MV 选项告诉 Clang 将整个文件名用双引号括起来,这是 NMake 和 Jom 使用的约定。

-femit-dwarf-unwind=<value>

何时发出 DWARF unwind(EH 帧)信息。这是一个特定于 Mach-O 的选项。

有效值为

  • no-compact-unwind - 仅当紧凑 unwind 编码不可用时才发出 DWARF unwind。这是 arm64 的默认值。

  • always - 无论如何始终发出 DWARF unwind。

  • default - 使用平台特定的默认值(对于所有非 arm64 平台,always)。

no-compact-unwind 是一种性能优化 - Clang 将发出更小的目标文件,这些文件可以被链接器更快地处理。但是,这可能会在较旧的 x86_64 目标上导致二进制兼容性问题,因此请谨慎使用。

-fdisable-block-signature-string

指示 clang 不要为块发出签名字符串。禁用字符串可能会破坏依赖它的现有代码。用户在使用此标志时应仔细考虑这种可能性。

配置文件

配置文件对命令行选项进行分组,并允许仅通过引用配置文件来指定所有选项。它们可能被用于,例如,收集调整编译以适应特定目标所需的选项,例如 -L-I-l--sysroot、代码生成选项等。

配置文件可以指定在命令行上,也可以从默认位置加载。如果两种变体都存在,则首先加载默认配置文件。

命令行选项 --config= 可用于在 Clang 调用中指定显式配置文件。如果该选项被使用多次,则所有指定的配置文件将按顺序加载。例如

clang --config=/home/user/cfgs/testing.txt
clang --config=debug.cfg --config=runtimes.cfg

如果提供的参数包含目录分隔符,则它被视为文件路径,并且从该文件读取选项。否则,该参数被视为文件名,并在以下目录中按顺序搜索:

  • 用户目录,

  • 系统目录,

  • Clang 可执行文件所在的目录。

配置文件的用户目录和系统目录既可以在构建时指定,也可以在运行时指定。在构建时,使用 CLANG_CONFIG_FILE_USER_DIRCLANG_CONFIG_FILE_SYSTEM_DIR。在运行时,使用 --config-user-dir=--config-system-dir= 命令行选项。在运行时指定配置目录将覆盖在构建时设置的配置目录。找到的第一个文件将被使用。如果找不到所需的文件,则为错误。

默认配置文件在以下段落中描述的规则下在相同的目录中搜索。可以通过传递 --no-default-config 标志来完全禁用加载默认配置文件。

首先,该算法将搜索名为 <triple>-<driver>.cfg 的配置文件,其中 triple 是正在构建的目标的 triple,而 driver 是当前使用的驱动程序的名称。该算法首先尝试使用用于驱动程序的规范名称,然后回退到在可执行文件名称中找到的名称。

以下规范驱动程序名称被使用:

  • clang 用于 gcc 驱动程序(用于编译 C 程序)

  • clang++ 用于 gxx 驱动程序(用于编译 C++ 程序)

  • clang-cpp 用于 cpp 驱动程序(纯预处理器)

  • clang-cl 用于 cl 驱动程序

  • flang 用于 flang 驱动程序

  • clang-dxc 用于 dxc 驱动程序

例如,当调用 x86_64-pc-linux-gnu-clang-g++ 时,驱动程序将首先尝试使用名为以下文件的配置文件:

x86_64-pc-linux-gnu-clang++.cfg

如果找不到此文件,它将尝试使用在可执行文件中找到的名称:

x86_64-pc-linux-gnu-clang-g++.cfg

请注意,诸如 --driver-mode=--target=-m32 之类的选项会影响搜索算法。例如,前面提到的可执行文件使用 -m32 参数调用时,将改为搜索以下文件:

i386-pc-linux-gnu-clang++.cfg

如果找不到上述任何文件,驱动程序将改为搜索单独的驱动程序和目标配置文件,并尝试加载两者。前者名为 <driver>.cfg,而后者名为 <triple>.cfg。与之前的变体类似,规范驱动程序名称将优先考虑,编译器将回退到实际名称。

例如,x86_64-pc-linux-gnu-clang-g++ 将尝试加载分别名为以下两个配置文件:

clang++.cfg
x86_64-pc-linux-gnu.cfg

并回退到尝试以下文件:

clang-g++.cfg
x86_64-pc-linux-gnu.cfg

如果找不到这些文件中的任何一个,都不会是错误。

配置文件由一行或多行上指定的命令行选项组成。仅由空格字符组成的行以及第一个非空白字符为 # 的行将被忽略。长选项可以通过尾随的反斜杠拆分到多行上。以下是一个配置文件的示例:

# Several options on line
-c --target=x86_64-unknown-linux-gnu

# Long option split between lines
-I/usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../\
include/c++/5.4.0

# other config files may be included
@linux.options

配置文件中由 @file 指令包含的文件相对于包含文件解析。例如,如果一个配置文件 ~/.llvm/target.cfg 包含指令 @os/linux.opts,则在目录 ~/.llvm/os 中搜索文件 linux.opts。另一种包含文件内容的方法是使用命令行选项 --config=。它的工作方式类似,但包含的文件使用配置文件的规则进行搜索。

要生成相对于配置文件的路径,可以使用 <CFGDIR> 令牌。这将扩展到包含配置文件的目录的绝对路径。

在配置文件与 SDK 内容一起部署的情况下,SDK 目录可以通过使用以 <CFGDIR> 为前缀的路径保持完全可移植。这样,用户可能只需要使用 --config= 指定一个根配置文件来使用编译器建立 SDK 的各个方面

--target=foo
-isystem <CFGDIR>/include
-L <CFGDIR>/lib
-T <CFGDIR>/ldscripts/link.ld

与语言和目标无关的功能

控制错误和警告

Clang 提供了许多方法来控制哪些代码结构会导致它发出错误和警告消息,以及如何将它们显示到控制台。

控制 Clang 如何显示诊断信息

当 Clang 发出诊断信息时,它会在输出中包含丰富的信息,并让您对要打印的信息进行细粒度控制。Clang 能够打印这些信息,以下是一些控制它的选项:

  1. 一个文件/行/列指示器,显示诊断信息在您的代码中的确切位置 [-fshow-column, -fshow-source-location]。

  2. 诊断的分类,包括提示、警告、错误和致命错误。

  3. 描述问题的文本字符串。

  4. 指示如何控制诊断的选项(对于支持该选项的诊断)[-fdiagnostics-show-option]。

  5. 诊断的高级类别,用于希望按类别分组诊断的客户端(对于支持该选项的诊断)[-fdiagnostics-show-category]。

  6. 发生问题的源代码行,以及指示重要位置的插入符号和范围[-fcaret-diagnostics]。

  7. “FixIt”信息,简要说明如何修复问题(当 Clang 确定知道如何修复时)[-fdiagnostics-fixit-info]。

  8. 涉及范围的机器可解析表示(默认情况下禁用)[-fdiagnostics-print-source-range-info]。

更多信息请参见诊断格式

诊断映射

所有诊断都映射到以下 6 个类别之一

  • 忽略

  • 提示

  • 备注

  • 警告

  • 错误

  • 致命

诊断类别

虽然默认情况下不显示,但每个诊断可能与一个高级类别相关联。此类别的目的是使能够以分组方式对产生大量错误或警告的构建进行分类。

类别默认情况下不显示,但可以使用 -fdiagnostics-show-category 选项启用。当设置为 “name” 时,类别将以文本形式打印在诊断输出中。当设置为 “id” 时,将打印类别号。类别名称到类别 ID 的映射可以通过运行 ‘clang   --print-diagnostic-categories’ 获得。

通过命令行标志控制诊断

待办事项:-W 标志、-pedantic 等

通过预处理指令控制诊断

Clang 还可以通过在源代码中使用预处理指令来控制启用哪些诊断。这对于关闭源代码部分中的特定警告很有用。Clang 支持 GCC 的预处理指令以确保与现有源代码兼容,因此 #pragma GCC diagnostic#pragma clang diagnostic 是 Clang 的同义词。不过,GCC 会忽略 #pragma clang diagnostic

预处理指令可以控制从命令行使用的任何警告。警告可以设置为忽略、警告、错误或致命。以下示例代码将告诉 Clang 或 GCC 忽略 -Wall 警告

#pragma GCC diagnostic ignored "-Wall"

Clang 还允许你推送和弹出当前警告状态。这在编写将由其他人编译的头文件时特别有用,因为你不知道他们使用什么警告标志进行构建。

在以下示例中,-Wextra-tokens 只对单行代码被忽略,之后诊断将恢复到之前存在的任何状态。

#if foo
#endif foo // warning: extra tokens at end of #endif directive

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wextra-tokens"

#if foo
#endif foo // no warning

#pragma GCC diagnostic pop

推送和弹出预处理指令将保存和恢复编译器的完整诊断状态,无论它如何设置。应该注意的是,虽然 Clang 支持 GCC 预处理指令,但 Clang 和 GCC 不支持完全相同的警告集,因此即使使用 GCC 兼容的 #预处理指令,也不能保证它们在两个编译器上的行为完全相同。

Clang 还不支持 GCC 的 #pragma diagnostic pop 行为,该行为没有相应的 #pragma diagnostic push。在这种情况下,GCC 假设源文件的最开始有一个 #pragma diagnostic push,因此“未配对的” #pragma diagnostic pop 与该隐式推送匹配。这对于 #pragma GCC diagnostic ignored 会产生影响,这些警告没有被推送和弹出保护。有关详细信息,请参考 GCC 文档

与 GCC 一样,Clang 接受 ignoredwarningerrorfatal 严重级别。它们可用于更改源文件区域中特定诊断的严重性。与 GCC 的一个显著区别是,无法通过此方式启用通过命令行参数启用的诊断。

一些与 -W 标志相关的诊断默认情况下具有错误严重性。它们可以被忽略或降级为警告

// C only
#pragma GCC diagnostic warning "-Wimplicit-function-declaration"
int main(void) { puts(""); }

除了控制编译器生成的警告和错误之外,还可以通过以下预处理指令生成自定义警告和错误消息

// The following will produce warning messages
#pragma message "some diagnostic message"
#pragma GCC warning "TODO: replace deprecated feature"

// The following will produce an error message
#pragma GCC error "Not supported"

这些预处理指令的操作方式类似于 #warning#error 预处理器指令,除了它们也可以通过 C99 _Pragma 运算符嵌入到预处理器宏中,例如

#define STR(X) #X
#define DEFER(M,...) M(__VA_ARGS__)
#define CUSTOM_ERROR(X) _Pragma(STR(GCC error(X " at line " DEFER(STR,__LINE__))))

CUSTOM_ERROR("Feature not available");

控制系统头文件中的诊断

当警告出现在系统头文件中时,它们会被抑制。默认情况下,如果在 -isystem 指定的包含路径中找到包含文件,则将其视为系统头文件,但这可以通过多种方式覆盖。

system_header 预处理指令可用于将当前文件标记为系统头文件。从预处理指令位置开始,在同一个文件中不会产生任何警告。

#if foo
#endif foo // warning: extra tokens at end of #endif directive

#pragma clang system_header

#if foo
#endif foo // no warning

命令行参数 –system-header-prefix=–no-system-header-prefix= 可用于覆盖包含路径的子集是否被视为系统头文件。当 #include 指令中的名称在头文件搜索路径中找到,并且以系统前缀开头时,该头文件被视为系统头文件。命令行中与指定头文件名称匹配的最后一个前缀优先。例如

$ clang -Ifoo -isystem bar --system-header-prefix=x/ \
    --no-system-header-prefix=x/y/

这里,#include "x/a.h" 被视为包含系统头文件,即使头文件是在 foo 中找到的,而 #include "x/y/b.h" 被视为不包含系统头文件,即使头文件是在 bar 中找到的。

如果包含文件被视为系统头文件,则相对于当前目录找到文件的 #include 指令被视为包含系统头文件。

控制 Clang 提供的 C 运行时头文件中的弃用诊断

Clang 负责提供一些平台 CRT 无法提供的 C 运行时头文件,例如实现限制或在独立模式下编译时。在包含此类 C 运行时头文件之前定义 _CLANG_DISABLE_CRT_DEPRECATION_WARNINGS 宏以禁用弃用警告。请注意,C 标准库头文件允许传递包含其他标准库头文件(参见 7.1.2p5),因此使用此宏的最合适方法是在构建系统中使用 -D 或在翻译单元中的任何包含指令之前设置此宏。

#define _CLANG_DISABLE_CRT_DEPRECATION_WARNINGS
#include <stdint.h>    // Clang CRT deprecation warnings are disabled.
#include <stdatomic.h> // Clang CRT deprecation warnings are disabled.

启用所有诊断

除了传统的 -W 标志外,还可以通过传递 -Weverything 来启用所有诊断。这与 -Werror 的预期效果相同,并且还包括 -pedantic 中的警告。一些诊断相互矛盾,因此,-Weverything 的用户经常会禁用许多诊断,例如 -Wno-c++98-compat-Wno-c++-compat,因为它们与最新的 C++ 标准相矛盾。

由于 -Weverything 启用了所有诊断,因此我们通常不建议使用它。对于大多数项目,-Wall -Wextra 是更好的选择。使用 -Weverything 意味着更新编译器更加困难,因为你暴露于实验性诊断,这些诊断的质量可能低于默认诊断。如果使用 -Weverything,我们建议你解决所有新增的编译器诊断,方法是修复所有它们发现的问题,或使用相应的 Wno- 选项显式禁用该诊断。

请注意,当与 -w(禁用所有警告)结合使用时,禁用所有警告会胜出。

控制静态分析器诊断

虽然严格来说不是编译器的一部分,但 Clang 的 静态分析器 的诊断也可以通过用户对源代码的更改来影响。有关更多信息,请参阅可用的 注释 和分析器的 常见问题解答页面

预编译头文件

预编译头文件 是一种通用方法,许多编译器使用这种方法来减少编译时间。这种方法的基本动机是,多个源文件通常会包含相同的(并且通常是大型的)头文件。因此,通过缓存编译器处理头文件所做的某些(冗余)工作,通常可以大大提高编译时间。预编译头文件(代表实现此优化的一种方法)实际上是文件,它们代表磁盘上的缓存,其中包含减少处理相应头文件所需工作量所需的关键信息。尽管预编译头文件的详细信息在不同编译器之间有所不同,但事实证明,预编译头文件在加速具有非常大的系统头文件的系统上的程序编译方面非常有效(例如,macOS)。

生成 PCH 文件

要使用 Clang 生成 PCH 文件,请使用 -x <language>-header 选项调用 Clang。这反映了 GCC 中生成 PCH 文件的接口

$ gcc -x c-header test.h -o test.h.gch
$ clang -x c-header test.h -o test.h.pch

使用 PCH 文件

当向 clang 传递 -include-pch 选项时,可以使用 PCH 文件作为前缀头文件

$ clang -include-pch test.h.pch test.c -o test

clang 驱动程序将检查 PCH 文件 test.h.pch 是否可用;如果可用,将从 PCH 文件处理 test.h(及其包含的文件)的内容。否则,Clang 将报告错误。

提示

Clang 不会自动为直接包含在源文件中的头文件或通过 -include 间接包含的头文件使用 PCH 文件。例如

$ clang -x c-header test.h -o test.h.pch
$ cat test.c
#include "test.h"
$ clang test.c -o test

在此示例中,clang 不会自动为 test.h 使用 PCH 文件,因为 test.h 是直接在源文件中包含的,而不是使用 -include-pch 在命令行中指定的。

可重定位的 PCH 文件

有时需要从尚未处于最终安装位置的头文件构建预编译头文件。例如,您可能在构建树中构建预编译头文件,然后将其安装在头文件旁边。Clang 允许创建“可重定位的”预编译头文件,这些头文件是在给定路径(到构建目录)下构建的,以后可以在安装位置使用。

要构建可重定位的预编译头文件,请将您的头文件放置在子目录中,该子目录的结构与安装位置相同。例如,如果您想为将安装到 /usr/include 中的头文件 mylib.h 构建预编译头文件,请创建一个名为 build/usr/include 的子目录,并将头文件 mylib.h 放置到该子目录中。如果 mylib.h 依赖于其他头文件,那么它们可以存储在 build/usr/include 中,并以类似于安装位置的方式进行存储。

构建可重定位的预编译头文件需要两个额外的参数。首先,传递 --relocatable-pch 标志,以指示生成的 PCH 文件应该是可重定位的。其次,传递 -isysroot /path/to/build,这会使库的所有包含都相对于构建目录。例如

# clang -x c-header --relocatable-pch -isysroot /path/to/build /path/to/build/mylib.h mylib.h.pch

加载可重定位的 PCH 文件时,将从系统头文件根目录中找到 PCH 文件中使用的各种头文件。例如,可以在 /usr/include/mylib.h 中找到 mylib.h。如果头文件安装在其他系统根目录中,则可以使用 -isysroot 选项提供一个不同的系统根目录,头文件将基于该目录。例如,-isysroot /Developer/SDKs/MacOSX10.4u.sdk 将在 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/mylib.h 中查找 mylib.h

可重定位的预编译头文件旨在用于编译环境受到严格控制并且无法在安装头文件后生成预编译头文件的少数情况下。

控制浮点数行为

Clang 提供了许多方法来控制浮点数行为,包括使用命令行选项和源代码编译指示。本节介绍各种浮点数语义模式以及相应的选项。

浮点数语义模式

模式

ffp-exception-behavior

{ignore, strict, maytrap}

fenv_access

{off, on}

(none)

frounding-math

{dynamic, tonearest, downward, upward, towardzero}

ffp-contract

{on, off, fast, fast-honor-pragmas}

fdenormal-fp-math

{IEEE, PreserveSign, PositiveZero}

fdenormal-fp-math-fp32

{IEEE, PreserveSign, PositiveZero}

fmath-errno

{on, off}

fhonor-nans

{on, off}

fhonor-infinities

{on, off}

fsigned-zeros

{on, off}

freciprocal-math

{on, off}

fallow-approximate-fns

{on, off}

fassociative-math

{on, off}

fcomplex-arithmetic

{basic, improved, full, promoted}

此表描述了与三种浮点数语义模型相对应的选项设置:精确(默认值)、严格和快速。

浮点数模型

模式

精确

严格

快速

积极

except_behavior

ignore

strict

ignore

ignore

fenv_access

off

on

off

off

rounding_mode

tonearest

dynamic

tonearest

tonearest

contract

on

off

fast

fast

support_math_errno

on

on

off

off

no_honor_nans

off

off

off

on

no_honor_infinities

off

off

off

on

no_signed_zeros

off

off

on

on

allow_reciprocal

off

off

on

on

allow_approximate_fns

off

off

on

on

allow_reassociation

off

off

on

on

complex_arithmetic

full

full

promoted

basic

-ffp-model 选项不会修改 fdenormal-fp-math 设置,但它会影响是否链接 crtfastmath.o。由于链接 crtfastmath.o 对程序有全局影响,并且由于可以在其他方式中更改全局非规格化处理,因此无法根据 fp-model 在任何函数中假设 fdenormal-fp-math 处理的状态。有关更多详细信息,请参阅 关于 crtfastmath.o 的说明

-ffast-math

启用快速数学模式。此选项允许编译器对浮点数数学进行激进的、可能存在损失的假设。这些包括

  • 浮点数数学服从实数的正则代数规则(例如,+* 是可结合的,x/y == x * (1/y),以及 (a + b) * c == a * c + b * c

  • 浮点数运算的运算数或结果不会出现 NaN 或无穷大值

  • +0-0 可能会被视为可互换的。

-ffast-math 还定义了 __FAST_MATH__ 预处理器宏。一些数学库识别此宏并更改其行为。除了 -ffp-contract=fast 外,使用以下任何选项来禁用 -ffast-math 中的任何一个单独优化都将导致不再设置 __FAST_MATH__-ffast-math 启用 -fcx-limited-range

此选项意味着

  • -fno-honor-infinities

  • -fno-honor-nans

  • -fapprox-func

  • -fno-math-errno

  • -ffinite-math-only

  • -fassociative-math

  • -freciprocal-math

  • -fno-signed-zeros

  • -fno-trapping-math

  • -fno-rounding-math

  • -ffp-contract=fast

注意:-ffast-math 导致 crtfastmath.o 与代码链接,除非存在 -shared-mno-daz-ftz。有关更多详细信息,请参阅 关于 crtfastmath.o 的说明

-fno-fast-math

禁用快速数学模式。此选项通过阻止编译器执行可能影响结果的任何转换,从而禁用不安全的浮点数优化。

此选项意味着

  • -fhonor-infinities

  • -fhonor-nans

  • -fno-approx-func

  • -fno-finite-math-only

  • -fno-associative-math

  • -fno-reciprocal-math

  • -fsigned-zeros

  • -ffp-contract=on

此外,此选项会将以下选项重置为其目标相关的默认值。

  • -f[no-]math-errno

关于 -ffp-contract-ffast-math-fno-fast-math 结合使用时的行为存在歧义。为了使 -ffp-contract 的值保持一致,我们定义了以下规则集

  • -ffast-mathffp-contract 设置为 fast

  • -fno-fast-math-ffp-contract 设置为 on(对于 CUDA 和 HIP,fast)。

  • 如果同时出现 -ffast-math-ffp-contract,但 -ffast-math 后面没有跟随 -fno-fast-math,则 ffp-contract 将被赋予最后看到的选项的值。

  • 如果出现 -fno-fast-math,并且 -ffp-contract 至少出现过一次,则 ffp-contract 将获得最后看到的 -ffp-contract 的值。

  • 如果出现 -fno-fast-math,但 -ffp-contract 没有出现过,则 -ffp-contract 的设置将由 -ffp-contract 的默认值决定。

注意:除非存在 -mdaz-ftz,否则 -fno-fast-math 会导致 crtfastmath.o 不与代码链接。

-fdenormal-fp-math=<value>

选择代码允许使用哪些非规格化数。

有效值为

  • ieee - IEEE 754 非规格化数

  • preserve-sign - 舍入到零的数字的符号将保留在 0 的符号中

  • positive-zero - 非规格化数将被舍入为正零

默认值取决于目标。对于大多数目标,默认值为 ieee

-f[no-]strict-float-cast-overflow

当浮点数在目标整型中不可表示时,根据语言标准,代码具有未定义的行为。默认情况下,Clang 不会保证在这种情况下有任何特定结果。使用 “no-strict” 选项,Clang 将改为饱和到最小和最大可表示整数值。NaN 将被转换为零。默认值为 -fstrict-float-cast-overflow

-f[no-]math-errno

要求数学函数通过设置 errno 来指示错误。默认值因工具链而异。 -fno-math-errno 允许可能导致标准 C 数学函数不设置 errno 的优化。例如,在某些系统上,数学函数 sqrt 被指定为在输入为负数时将 errno 设置为 EDOM。在这些系统上,编译器通常无法在不进行额外检查以确保 errno 被适当地设置的情况下,优化对 sqrt 的调用以使用内联代码(例如 x86 sqrtsd 指令)。 -fno-math-errno 允许这些转换。

在某些目标上,数学库函数从不设置 errno,因此 -fno-math-errno 是默认值。这包括大多数 BSD 派生系统,包括 Darwin。

-f[no-]trapping-math

控制浮点异常行为。 -fno-trapping-math 允许假设浮点运算不会产生诸如除以零、溢出和下溢等陷阱的优化。

  • 选项 -ftrapping-math 的行为与 -ffp-exception-behavior=strict 相同。

  • 选项 -fno-trapping-math 的行为与 -ffp-exception-behavior=ignore 相同。这是默认值。

-ffp-contract=<value>

指定编译器何时允许形成融合浮点运算,例如融合乘加(FMA)。融合运算允许生成比单独执行相同运算更精确的结果。

C 标准允许表达式中的中间浮点结果以比其类型通常允许的更高精度计算。这允许运算融合,Clang 默认情况下会利用这一点。这种行为可以使用 FP_CONTRACTclang fp contract 编译指示控制。请参阅编译指示文档,了解有关编译指示如何与该选项交互的描述。

有效值为

  • fast(跨语句融合,忽略编译指示,CUDA 的默认值)

  • on(在同一语句中融合,除非由编译指示规定,否则为除 CUDA/HIP 之外的语言的默认值)

  • off(从不融合)

  • fast-honor-pragmas(跨语句融合,除非由编译指示规定,否则为 HIP 的默认值)

-f[no-]honor-infinities

允许假设参数和结果不为 +-Inf 的浮点优化。默认值为 -fhonor-infinities

如果同时使用 -fno-honor-infinities-fno-honor-nans,则效果与指定 -ffinite-math-only 相同。

-f[no-]honor-nans

允许假设参数和结果不为 NaN 的浮点优化。默认值为 -fhonor-nans

如果同时使用 -fno-honor-infinities-fno-honor-nans,则效果与指定 -ffinite-math-only 相同。

-f[no-]approx-func

允许将某些数学函数调用(例如 logsqrtpow 等)替换为大约等效的指令集或替代数学函数调用。例如,pow(x, 0.25) 可以替换为 sqrt(sqrt(x)),尽管在 x-0.0-inf 的情况下,这是一个不精确的结果。默认值为 -fno-approx-func

-f[no-]signed-zeros

允许忽略浮点零符号的优化。默认值为 -fsigned-zeros

-f[no-]associative-math

允许重新关联浮点运算。默认值为 -fno-associative-math

-f[no-]reciprocal-math

允许将除法运算转换为乘以倒数。这可能比普通的除法快得多,但精度也可能低得多。默认值为 -fno-reciprocal-math

-f[no-]unsafe-math-optimizations

允许不安全的浮点优化。 -funsafe-math-optimizations 还意味着

  • -fapprox-func

  • -fassociative-math

  • -freciprocal-math

  • -fno-signed-zeros

  • -fno-trapping-math

  • -ffp-contract=fast

-fno-unsafe-math-optimizations 意味着

  • -fno-approx-func

  • -fno-associative-math

  • -fno-reciprocal-math

  • -fsigned-zeros

  • -ffp-contract=on

关于 -ffp-contract-funsafe-math-optimizations-fno-unsafe-math-optimizations 组合时如何表现存在歧义。 -fno-fast-math 中的说明也适用于这些选项。

默认值为 -fno-unsafe-math-optimizations

-f[no-]finite-math-only

允许假设参数和结果不为 NaN 或 +-Inf 的浮点优化。 -ffinite-math-only 定义了 __FINITE_MATH_ONLY__ 预处理宏。 -ffinite-math-only 意味着

  • -fno-honor-infinities

  • -fno-honor-nans

-ffno-inite-math-only 意味着

  • -fhonor-infinities

  • -fhonor-nans

默认值为 -fno-finite-math-only

-f[no-]rounding-math

默认情况下,强制浮点运算遵守动态设置的舍入模式。

浮点运算的结果通常无法在结果类型中精确表示,因此必须进行舍入。IEEE 754 描述了控制如何执行这种舍入的不同舍入模式,并非所有这些模式都得到所有实现的支持。C 提供了接口(fesetroundfesetenv)用于动态控制舍入模式,虽然它也建议更改舍入模式的某些约定,但这些约定通常不会在 ABI 中强制执行。由于舍入模式会改变运算的数值结果,因此编译器必须了解一些有关舍入模式的信息才能优化浮点运算。

请注意,作为常量初始化一部分执行的浮点运算正式在程序启动之前执行,因此不受当前舍入模式的影响。这包括全局变量和局部 static 变量的初始化。这些上下文中的浮点运算将使用 FE_TONEAREST 舍入。

  • 选项 -fno-rounding-math 允许编译器假设舍入模式设置为 FE_TONEAREST。这是默认值。

  • 选项 -frounding-math 强制编译器遵守动态设置的舍入模式。这将阻止可能影响结果的优化,如果舍入模式发生更改或与默认值不同;例如,它会阻止浮点运算在大多数调用中重新排序,并在结果无法精确表示时阻止常量折叠。

-ffp-model=<value>

指定浮点行为。 -ffp-model 是一个包含其他单一用途浮点选项功能的总括选项。有效值为: precisestrictfastaggressive。详情

  • precise 禁用对浮点数据不值安全的优化,尽管 FP 压缩 (FMA) 已启用 (-ffp-contract=on)。这是默认行为。此值将 -fmath-errno 重置为其目标相关的默认值。

  • strict 启用 -frounding-math-ffp-exception-behavior=strict,并禁用压缩 (FMA)。所有 -ffast-math 启用都已禁用。启用 STDC FENV_ACCESS:默认情况下, FENV_ACCESS 已禁用。此选项设置的行为就如同 #pragma STDC FENV_ACCESS ON 出现在源文件顶部。

  • fast 的行为与指定 -funsafe-math-optimizations-fno-math-errno-fcomplex-arithmetic=promoted ffp-contract=fast 相同。

  • aggressive 的行为与同时指定 -ffast-mathffp-contract=fast 相同。

注意:如果您的命令行指定了多个 -ffp-model 选项实例,或者您的命令行选项指定了 -ffp-model,然后在命令行中选择了具有否定已选择的 ffp-model 部分效果的浮点选项,则编译器将发出诊断警告,表明已发生覆盖。

-ffp-exception-behavior=<value>

指定浮点异常行为。

有效值为: ignoremaytrapstrict。默认值为 ignore。详情

  • ignore 编译器假定不会读取异常状态标志,并且浮点异常将被屏蔽。

  • maytrap 编译器避免可能引发原本不会由原始代码引发的异常的转换。编译器执行的常量折叠不受此选项影响。

  • strict 编译器确保所有转换严格保留原始代码的浮点异常语义。

-ffp-eval-method=<value>

指定代码中单个表达式内的中间结果的浮点求值方法。

有效值为: sourcedoubleextended。对于 64 位目标,默认值为 source。但是,对于 32 位 x86 目标,在 NETBSD 6.99.26 及以下版本的情况下,默认值为 double;在 NETBSD 大于 6.99.26 的情况下,如果使用 NoSSE,默认值为 extended,如果使用 SSE,默认值为 source。详情

  • source 编译器使用源程序中声明的浮点类型作为求值方法。

  • double 编译器使用 double 作为所有类型比 double 窄的浮点表达式的浮点求值方法。

  • extended 编译器使用 long double 作为所有类型比 long double 窄的浮点表达式的浮点求值方法。

-f[no-]protect-parens

此选项适用于浮点类型、具有浮点组件的复数类型以及这些类型的向量。根据 C 和 C++ 语言标准在数学上正确且允许的某些算术表达式转换,在处理浮点类型时可能不正确,例如重新关联和分配。此外,优化器在计算算术表达式时可能会忽略括号,在这种情况下,带括号和不带括号的表达式表示相同的数学值。例如,(a+b)+c 与 a+(b+c) 具有相同的数学值,但优化器可以自由地以任何顺序评估加法,而不管括号如何。启用此选项后,将强制优化器在所有情况下都遵守运算符优先级方面的括号顺序。默认值为 -fno-protect-parens

请注意,启用 -fprotect-parens 时,浮点压缩(选项 -ffp-contract=)将被禁用。另请注意,在安全的浮点模式下,例如 -ffp-model=precise-ffp-model=strict,此选项没有任何效果,因为优化器被禁止进行不安全的转换。

-fexcess-precision:

C 和 C++ 标准允许浮点表达式被计算,就好像中间结果比表达式严格允许的类型具有更高的精度(和/或更广的范围)一样。这被称为超精度算术。超精度算术可以提高结果的准确性(尽管并不总是如此),如果目标缺乏对特定类型算术的直接硬件支持,它可以使计算速度显着提高。但是,它也可能破坏严格的浮点可重复性。

在标准下,赋值和显式强制转换强制操作数转换为其正式类型,丢弃任何超精度。由于数据只能通过赋值在语句之间流动,这意味着超精度算术的使用是单个语句的可靠局部属性,结果不会根据优化而改变。但是,当使用超精度算术时,Clang 不保证严格的可重复性,未来的编译器版本可能会识别更多使用超精度算术的机会,例如与浮点内置函数一起使用。

Clang 不会对大多数类型或大多数目标使用超精度算术。例如,即使在必须在 80 位 X87 格式中执行 floatdouble 计算的预 SSE X86 目标上,Clang 也将所有中间结果正确舍入到其类型。目前,Clang 仅默认对以下类型和目标使用超精度算术

  • 在没有 AVX512-FP16 的 X86 目标上的 _Float16

选项 -fexcess-precision=<value> 可用于控制超精度算术的使用。有效值为

  • standard - 默认值。在 C 和 C++ 标准的约束下允许使用超精度算术。除了上面列出的类型和目标之外,没有任何效果。

  • fast - 为了与 GCC 兼容而被接受,但目前被视为 standard 的别名。

  • 16 - 强制 _Float16 运算被发出,而不使用超精度算术。

-fcomplex-arithmetic=<value>:

此选项指定复数乘法和除法的实现。

有效值为: basicimprovedfullpromoted

  • basic 使用源精度上的代数公式实现复数除法和乘法。没有特殊处理以避免溢出。NaN 和无限值不会被处理。

  • improved 使用源精度上的 Smith 算法实现复数除法。Smith 用于复数除法的算法。参见 SMITH, R. L. 算法 116:复数除法。Commun. ACM 5, 8 (1962)。此值提供了对中间计算溢出的改进处理,但可能会发生溢出。在某些情况下,不会处理 NaN 和无限值。

  • full 使用对运行时库函数的调用实现复数除法和乘法(通常情况,但 BE 可能会在它了解足够关于输入的潜在范围时替换库调用)。溢出和非有限值由库实现处理。对于乘法,溢出将根据正常的浮点规则发生。这是默认值。

  • promoted 使用更高精度的代数公式实现复数除法。 处理溢出。 在某些情况下处理非有限值。 如果目标没有对更高精度的數據類型提供原生支持,则将使用使用 Smith 算法的复数运算实现。 在某些情况下,仍然可能发生溢出。 NaN 和无穷值不处理。

-fcx-limited-range:

此选项与 -fcomplex-arithmetic=basic 别名。 它为复数除法和乘法启用朴素的数学公式,而不进行结果的 NaN 检查。 默认值为 -fno-cx-limited-range,与 -fcomplex-arithmetic=full 别名。 此选项由 -ffast-math 选项启用。

-fcx-fortran-rules:

此选项与 -fcomplex-arithmetic=improved 别名。 它为复数乘法启用朴素的数学公式,并启用对复数除法应用 Smith 算法。 参阅 SMITH, R. L. 算法 116: 复数除法。 Commun. ACM 5, 8 (1962)。 默认值为 -fno-cx-fortran-rules,与 -fcomplex-arithmetic=full 别名。

访问浮点环境

许多目标允许配置浮点运算来控制诸如如何舍入不精确结果以及如何处理异常条件等方面。 此配置称为浮点环境。 C 和 C++ 默认情况下限制对浮点环境的访问,并且编译器可以假定所有操作都在默认环境中执行。 当代码在此默认模式下编译时,依赖于环境的操作(例如浮点运算和 FLT_ROUNDS)如果动态环境不是默认环境,则可能具有未定义的行为;例如,FLT_ROUNDS 可能或可能不会简单地返回其目标的默认值,而不是读取动态环境,并且浮点运算可以被优化,就好像动态环境是默认环境一样。 同样,在此默认模式下更改浮点环境也是未定义的行为,例如通过调用 fesetround 函数。 C 提供了两个编译指示,允许代码动态地修改浮点环境。

  • #pragma STDC FENV_ACCESS ON 允许对整个浮点环境进行动态更改。

  • #pragma STDC FENV_ROUND FE_DYNAMIC 允许仅对浮点舍入模式进行动态更改。 这可能比 FENV_ACCESS ON 更容易优化,因为编译器默认情况下仍然可以忽略浮点异常的可能性。

这两个编译指示都可以在块范围的开头使用,在这种情况下,它们将覆盖该范围中的所有代码(除非它们在子范围中被关闭),或者可以在文件的顶层使用,在这种情况下,它们将覆盖所有随后的函数体,直到它们被关闭。 请注意,从覆盖了其中一个编译指示的代码中进入未被其中一个编译指示覆盖的代码是未定义的行为,除非浮点环境已恢复为其默认状态。 有关这些编译指示的更多信息,请参阅 C 标准。

命令行选项 -frounding-math 的行为就好像翻译单元以 #pragma STDC FENV_ROUND FE_DYNAMIC 开头一样。 命令行选项 -ffp-model=strict 的行为就好像翻译单元以 #pragma STDC FENV_ACCESS ON 开头一样。

只想对特定浮点运算使用特定舍入模式的代码可以通过使用 #pragma STDC FENV_ROUND 以及除 FE_DYNAMIC 之外的其他值来避免动态浮点环境的大部分危险。

关于 crtfastmath.o 的说明

-ffast-math-funsafe-math-optimizations 在没有 -shared 选项的情况下,会导致自动链接 crtfastmath.o,这将添加一个静态构造函数,该构造函数设置 MXCSR 中的 FTZ/DAZ 位,这不仅会影响当前编译单元,还会影响程序中包含的所有静态和共享库。 可以通过使用 -mdaz-ftz-mno-daz-ftz 标志来覆盖此决定,以分别链接或不链接 crtfastmath.o

关于 __FLT_EVAL_METHOD__ 的说明

__FLT_EVAL_METHOD__ 未定义为传统的宏,因此在转储预处理器宏时不会出现。 相反,__FLT_EVAL_METHOD__ 展开到的值是在展开时由 -ffp-eval-method 命令行选项或目标设置的值确定的。 这是因为 __FLT_EVAL_METHOD__ 宏在存在更改评估方法的 #pragma 时无法展开到正确的评估方法。 如果在 #pragma clang fp eval_method 修改的范围内展开 __FLT_EVAL_METHOD__,则会发出错误消息。

关于浮点常量评估的说明

在 C 中,浮点运算在翻译期间保证被评估的唯一位置是在静态存储期限变量的初始化程序中,这些变量都在程序开始执行之前(以及在非默认浮点环境可以进入之前)被名义上初始化。 但是 C++ 有更多浮点常量评估发生的情况。 具体来说:对于静态/线程局部变量,首先尝试在常量上下文中评估初始化程序,包括在常量浮点环境中(就像在 C 中一样),然后,如果失败,则回退到发出运行时代码来执行初始化(这可能通常在不同的浮点环境中)。

以用 -frounding-math 编译时为例

constexpr float func_01(float x, float y) {
  return x + y;
}
float V1 = func_01(1.0F, 0x0.000001p0F);

C++ 规则是,静态存储期限变量的初始化程序首先在翻译期间评估(因此,在默认舍入模式下),并且只有在编译时评估失败时才在运行时评估(因此,在运行时舍入模式下)。 这与 C 规则一致;C11 F.8.5 说:所有自动初始化的计算都在(就好像)执行时完成;因此,它受任何有效模式的影响,并根据 IEC 60559 提出浮点异常(前提是 FENV_ACCESS 编译指示的状态为 ‘‘on’’)。 所有对具有静态或线程存储期限的对象的初始化的计算都在(就好像)翻译时完成。 C++ 通过添加另一个初始化阶段(在运行时)来概括这一点,如果翻译时初始化失败,但初始化程序的翻译时评估成功,它将被视为常量初始化程序。

控制代码生成

Clang 提供了许多方法来控制代码生成。 选项列在下面。

-f[no-]sanitize=check1,check2,...

打开对各种形式的未定义或可疑行为的运行时检查。

此选项控制 Clang 是否为各种形式的未定义或可疑行为添加运行时检查,默认情况下禁用此选项。 如果检查失败,则会在运行时生成一条诊断消息,说明问题。 主要检查有

  • -fsanitize=address: AddressSanitizer,一种内存错误检测器。

  • -fsanitize=thread: ThreadSanitizer,一种数据竞争检测器。

  • -fsanitize=memory: MemorySanitizer,一种未初始化读取检测器。 需要对所有程序代码进行检测。

  • -fsanitize=undefined: UndefinedBehaviorSanitizer,一种快速且兼容的未定义行为检查器。

  • -fsanitize=dataflow: DataFlowSanitizer,一种通用数据流分析器。

  • -fsanitize=cfi: 控制流完整性 检查。 需要 -flto

  • -fsanitize=kcfi: 内核间接调用前向边控制流完整性。

  • -fsanitize=safe-stack: 安全堆栈 防护,防止基于堆栈的内存损坏错误。

  • -fsanitize=realtime: 实时安全检查器,一种实时安全检查器。

还有更多细粒度的检查可用:请参阅 列表,其中包含可以检测到的特定类型的未定义行为,以及 列表,其中包含控制流完整性方案。

链接时也必须提供 -fsanitize= 参数,以便链接到适当的运行时库。

无法在一个程序中组合多个 -fsanitize=address-fsanitize=thread-fsanitize=memory 检查器。

-f[no-]sanitize-recover=check1,check2,...
-f[no-]sanitize-recover[=all]

控制由 -fsanitize= 标志启用的检查是否为非致命性。如果检查是致命的,则程序将在检测到此类第一个错误后停止,并打印错误报告。

默认情况下,非致命检查是那些由 UndefinedBehaviorSanitizer 启用的检查,除了 -fsanitize=return-fsanitize=unreachable。一些消毒剂可能不支持恢复(或者默认情况下不支持,例如 AddressSanitizer),并且在检测到问题后始终会使程序崩溃。

请注意,-fsanitize-trap 标志优先于此标志。这意味着,如果某个检查已在命令行中的其他位置配置为陷阱,或者如果检查默认情况下会触发陷阱,则此标志将不会产生任何影响,除非该消毒剂的触发行为通过 -fno-sanitize-trap 禁用。

例如,如果命令行包含标志 -fsanitize=undefined -fsanitize-trap=undefined,则标志 -fsanitize-recover=alignment 本身不会产生任何影响;它需要与 -fno-sanitize-trap=alignment 结合使用。

-f[no-]sanitize-trap=check1,check2,...
-f[no-]sanitize-trap[=all]

控制由 -fsanitize= 标志启用的哪些检查会触发陷阱。此选项旨在用于消毒剂运行时无法使用的情况(例如,在构建 libc 或内核模块时),或者消毒剂运行时引起的二进制大小增加是一个问题的情况。

此标志仅与 控制流完整性 方案和 UndefinedBehaviorSanitizer 检查(除了 vptr)兼容。

此标志默认情况下为 cfi 组中的消毒剂启用。

-fsanitize-ignorelist=/path/to/ignorelist/file

禁用或修改文件中列出的对象(源文件、函数、变量、类型)的消毒剂检查。有关文件格式描述,请参阅 Sanitizer 特殊情况列表

-fno-sanitize-ignorelist

不要使用忽略列表文件,如果它在命令行中先前已指定。

-f[no-]sanitize-coverage=[type,features,...]

除了某些消毒剂之外,还可以启用简单的代码覆盖率。有关更多详细信息,请参阅 SanitizerCoverage

-f[no-]sanitize-address-outline-instrumentation

控制如何生成地址消毒剂代码。如果启用,将始终使用函数调用,而不是内联代码。打开此选项可能会减小二进制文件的大小,但可能会导致运行时性能下降。

有关更多详细信息,请参阅 :doc: AddressSanitizer

-f[no-]sanitize-stats

启用为启用的消毒剂收集简单的统计信息。有关更多详细信息,请参阅 SanitizerStats

-fsanitize-undefined-trap-on-error

已弃用的 -fsanitize-trap=undefined 的别名。

-fsanitize-cfi-cross-dso

启用跨 DSO 控制流完整性检查。此标志修改了 cfi 组中消毒剂的行为,以允许检查跨 DSO 的虚拟调用和间接调用。

-fsanitize-cfi-icall-generalize-pointers

在控制流完整性间接调用检查检查的函数类型签名中,将返回值和参数类型中的指针泛化。有关更多详细信息,请参阅 Control Flow Integrity

-fsanitize-cfi-icall-experimental-normalize-integers

在控制流完整性间接调用检查检查的函数类型签名中,规范返回值和参数类型中的整数。有关更多详细信息,请参阅 Control Flow Integrity

此选项目前处于实验阶段。

-fstrict-vtable-pointers

启用基于严格规则的优化,这些规则用于覆盖多态 C++ 对象,即 vptr 在对象的生命周期内是不变的。这可以实现更好的去虚拟化。默认情况下处于关闭状态,因为它仍在实验阶段。

-fwhole-program-vtables

为具有 隐藏 LTO 可见性 的类启用全程序 vtable 优化,例如单实现去虚拟化和虚拟常量传播。需要 -flto

-f[no]split-lto-unit

控制使用 -flto=thin 编译时将 LTO 单位 拆分为常规 LTO 和 ThinLTO 部分。默认情况下为 false,除非指定了 -fsanitize=cfi-fwhole-program-vtables,在这种情况下默认情况下为 true。使用 fsanitize=cfi 需要拆分,并且通过 -fno-split-lto-unit 禁用拆分会导致错误。使用 -fwhole-program-vtables 时,拆分是可选的,但是,它可以实现更激进的全程序 vtable 优化(特别是虚拟常量传播)。

启用时,vtable 定义和选定的虚拟函数将放置在拆分的常规 LTO 模块中,从而实现 CFI 和虚拟常量传播所需的更激进的全程序 vtable 优化。但是,这可能会增加 LTO 链接时间和内存需求,因为它会合并所有拆分的常规 LTO 模块,并使用常规 LTO 对其进行 LTO 链接。

-fforce-emit-vtables

为了改进去虚拟化,即使在不需要的情况下,也会强制发出 vtable,即使在不需要的模块中也会发出 vtable。它会导致更多内联虚拟函数被发出。

-fno-assume-sane-operator-new

不要假设 C++ 的 new 运算符是安全的。

此选项告诉编译器不要假设 C++ 的全局 new 运算符在函数返回时始终返回一个不与任何其他指针重叠的指针。

-fassume-nothrow-exception-dtor

假设异常对象的析构函数不会抛出异常,并为 catch 处理程序生成更少的代码。对具有可能抛出异常的析构函数的类型的抛出表达式会导致错误。

默认情况下,Clang 假设异常对象可能具有抛出异常的析构函数。对于 Itanium C++ ABI,Clang 会生成一个着陆垫来销毁局部变量,并为代码 catch (...) { ... } 调用 _Unwind_Resume。此选项告诉 Clang 异常对象的析构函数不会抛出异常,并且可以简化代码。

-ftrap-function=[name]

指示代码生成器为 __builtin_trap() 发出对指定函数名的函数调用。

LLVM 代码生成器将 __builtin_trap() 转换为陷阱指令(如果目标 ISA 支持)。否则,内置函数将转换为对 abort 的调用。如果设置了此选项,则代码生成器将始终将内置函数降低为对指定函数的调用,而不管目标 ISA 是否具有陷阱指令。此选项对于无法正确处理陷阱的环境(例如深度嵌入)或需要某些自定义行为时很有用。

-ftls-model=[model]

选择要使用的 TLS 模型。

有效值为:global-dynamiclocal-dynamicinitial-execlocal-exec。默认值为 global-dynamic。如果目标不支持选定的模型,或者可以使用更有效的模型,则编译器可以使用不同的模型。TLS 模型可以使用 tls_model 属性在每个变量的基础上进行覆盖。

-femulated-tls

选择模拟的 TLS 模型,该模型将覆盖所有 -ftls-model 选择。

在模拟的 TLS 模式下,对 TLS 变量的所有访问都将转换为对运行时库中的 __emutls_get_address 的调用。

-mhwdiv=[values]

选择支持硬件除法指令的 ARM 模式(arm 或 thumb)。

有效值为:armthumbarm,thumb。此选项用于指示支持硬件除法指令的模式(arm 或 thumb)。这仅适用于 ARM 架构。

-m[no-]crc

启用或禁用 CRC 指令。

此选项用于指示是否生成 CRC 指令。这仅适用于 ARM 架构。

CRC 指令在 ARMv8 上默认启用。

-mgeneral-regs-only

生成仅使用通用寄存器的代码。

此选项将生成的代码限制为仅使用通用寄存器。这仅适用于 AArch64 架构。

-mcompact-branches=[values]

控制 MIPSR6 中紧凑分支的使用。

有效值为:neveroptimalalways。默认值为 optimal,在延迟槽无法填充时生成紧凑分支。 never 禁用紧凑分支的使用,而 always 尽可能生成紧凑分支。

-f[no-]max-type-align=[number]

指示代码生成器在通过不透明指针或引用访问内存时,不要强制执行高于给定数字(以字节为单位)的对齐。当直接访问变量或被指向类型具有显式的“对齐”属性时,此限制将被忽略。

该值通常应由系统分配器的属性决定。某些内置类型,尤其是向量类型,具有非常高的自然对齐方式;在处理这些类型的数值时,Clang 通常希望使用利用这种对齐方式的指令。但是,许多系统分配器不会承诺返回比 8 字节或 16 字节对齐更对齐的内存。使用此选项限制编译器可以为任意指针假设的对齐方式,该指针可能指向堆。

此选项不影响类型的 ABI 对齐方式;结构体和联合体的布局以及 alignof 运算符返回的值保持不变。

此选项可以通过在结构体、联合体或类型定义上设置显式的“对齐”对齐方式来逐个案例覆盖。例如

#include <immintrin.h>
// Make an aligned typedef of the AVX-512 16-int vector type.
typedef __v16si __aligned_v16si __attribute__((aligned(64)));

void initialize_vector(__aligned_v16si *v) {
  // The compiler may assume that ‘v’ is 64-byte aligned, regardless of the
  // value of -fmax-type-align.
}
-faddrsig, -fno-addrsig

控制 Clang 是否将地址显著性表发射到目标文件中。地址显著性表允许链接器实现 safe ICF,而不会产生其他实现技术(如重定位扫描)可能导致的误报。在使用集成汇编器时,地址显著性表在 ELF 目标上默认启用。此标志目前仅对 ELF 目标有影响。

-f[no]-unique-internal-linkage-names

控制 Clang 是否为内部链接符号发射唯一的(尽力而为的)符号名称。当设置此选项时,编译器会对命令行中的主源文件路径进行哈希处理,并将结果附加到所有内部符号。如果一个程序包含多个使用相同命令行源文件路径编译的目标文件,则不能保证符号是唯一的。此选项在将配置文件信息归因于正确的函数时特别有用,尤其是在二进制文件中存在多个具有相同私有链接名称的函数时。

需要注意的是,此选项不能保证唯一性,以下是一个当两个模块包含具有相同私有链接名称的符号时不唯一的例子。

$ cd $P/foo && clang -c -funique-internal-linkage-names name_conflict.c
$ cd $P/bar && clang -c -funique-internal-linkage-names name_conflict.c
$ cd $P && clang foo/name_conflict.o && bar/name_conflict.o
-f[no]-basic-block-address-map:
Emits a ``SHT_LLVM_BB_ADDR_MAP`` section which includes address offsets for each
basic block in the program, relative to the parent function address.
-fbasic-block-sections=[all, list=<arg>, none]

控制 Clang 如何为基本块发射文本节。对于值 alllist=<arg>,每个基本块或基本块子集都可以放置在它自己的唯一节中。

使用 list=<arg> 选项时,可以指定一个包含需要放置在唯一节中的基本块子集的文件。文件的格式如下。例如,list=spec.txt,其中 spec.txt 如下

!foo
!!2
!_Z3barv

将把函数 fooid 2 的机器基本块放置在一个唯一节中。它还将把函数 bar 的所有基本块放置在唯一节中。

此外,还可以使用 list=<arg> 选项指定节集群。例如,list=spec.txt,其中 spec.txt 包含

!foo
!!1 !!3 !!5
!!2 !!4 !!6

将为函数 foo 创建两个唯一节,第一个包含奇数编号的基本块,第二个包含偶数编号的基本块。

基本块节允许链接器重新排序基本块,并启用链接时优化,例如全程序跨过程基本块重新排序。

-fcodegen-data-generate[=<path>]

将原始代码生成 (CG) 数据发射到目标文件中的自定义节中。目前,此选项还将来自目标文件的原始 CG 数据合并到由 <path> 指定的索引 CG 数据文件中,仅限 LLD MachO。当 <path> 未指定时,将创建 default.cgdata。CG 数据文件组合了每个目标文件中本地发生的 outline 实例。

$ clang -fuse-ld=lld -Oz -fcodegen-data-generate code.cc

对于尚不支持此功能的链接器,可以手动使用 llvm-cgdata 合并目标文件中的此 CG 数据。

$ clang -c -fuse-ld=lld -Oz -fcodegen-data-generate code.cc
$ llvm-cgdata --merge -o default.cgdata code.o
-fcodegen-data-use[=<path>]

从指定路径读取代码生成数据,以更有效地跨编译单元 outline 函数。当 <path> 未指定时,将使用 default.cgdata。此选项可以创建许多相同的 outline 函数,这些函数可以通过传统链接器的相同代码折叠 (ICF) 进行优化。

$ clang -fuse-ld=lld -Oz -Wl,--icf=safe -fcodegen-data-use code.cc

配置文件引导优化

配置文件信息可以实现更好的优化。例如,知道分支被频繁地执行有助于编译器在排序基本块时做出更好的决策。知道函数 foo 比另一个函数 bar 更频繁地被调用有助于内联。优化级别 -O2 及更高建议用于配置文件引导优化。

Clang 支持使用两种不同的配置文件方法进行配置文件引导优化。采样探查器可以生成运行时开销非常低的配置文件,或者您可以构建代码的已插桩版本,该版本可以收集更详细的配置文件信息。这两种类型的配置文件都可以提供代码中指令的执行次数以及分支执行和函数调用的信息。

无论使用哪种类型的配置文件,都要注意通过使用代表典型行为的输入运行代码来收集配置文件。配置文件中未执行的代码将被优化,就好像它不重要一样,并且编译器可能会对在配置文件时过度使用但实际上不重要的代码做出不佳的优化选择。

采样和插桩之间的区别

尽管这两种技术用于类似的目的,但它们之间存在重要区别。

  1. 用一种技术生成的配置文件数据不能被另一种技术使用,也没有任何转换工具可以将一种转换为另一种。因此,通过 -fprofile-generate-fprofile-instr-generate 生成的配置文件必须与 -fprofile-use-fprofile-instr-use 一起使用。类似地,必须将外部探查器生成的采样配置文件转换为 -fprofile-sample-use-fauto-profile 的格式才能使用。

  2. 插桩配置文件数据可用于代码覆盖率分析和优化。

  3. 采样配置文件只能用于优化。它们不能用于代码覆盖率分析。尽管从技术上讲,可以使用采样配置文件进行代码覆盖率分析,但基于样本的配置文件对于代码覆盖率分析来说过于粗粒度;它将产生很差的结果。

  4. 采样配置文件必须由外部工具生成。该工具生成的配置文件必须转换为 LLVM 可以读取的格式。采样分析器部分描述了支持的采样配置文件格式之一。

使用采样分析器

采样分析器用于收集应用程序执行期间的运行时信息,例如硬件计数器。它们通常非常高效,不会产生很大的运行时开销。分析器收集的样本数据可以在编译期间用于确定代码执行最多的区域。

使用样本分析器的数据需要对程序的构建方式进行一些更改。在编译器可以使用分析信息之前,代码需要在分析器下执行。以下是使用样本分析器进行优化的常见构建周期

  1. 使用源代码行表信息构建代码。您可以使用所有通常用于构建应用程序的构建标志。唯一的要求是生成包含源代码行信息的 DWARF 调试信息。此 DWARF 信息对于分析器能够将指令映射回源代码行位置非常重要。可以使用 -fdebug-info-for-profiling-funique-internal-linkage-names 选项来提高此 DWARF 信息的有效性。

    在 Linux 上

    $ clang++ -O2 -gline-tables-only \
      -fdebug-info-for-profiling -funique-internal-linkage-names \
      code.cc -o code
    

    虽然 MSVC 风格的目标默认使用 CodeView 调试信息,但生成源代码级 LLVM 配置文件需要 DWARF 调试信息。使用 -gdwarf 来包含 DWARF 调试信息

    > clang-cl /O2 -gdwarf -gline-tables-only ^
      /clang:-fdebug-info-for-profiling /clang:-funique-internal-linkage-names ^
      code.cc /Fe:code /fuse-ld=lld /link /debug:dwarf
    

提示

-funique-internal-linkage-names 根据给定的命令行源文件路径生成唯一的名称。如果您的构建系统使用绝对源路径,并且这些路径可能在步骤 1 和 4 之间发生变化,则唯一的函数名称可能会发生变化,并导致未使用的配置文件数据。在这种情况下,请考虑省略此选项。

  1. 在采样分析器下运行可执行文件。您使用的特定分析器并不重要,只要它的输出可以转换为 LLVM 优化器理解的格式即可。

    两个这样的分析器是 Linux Perf 分析器 (https://perf.wiki.kernel.org/) 和英特尔的采样启用产品 (SEP),作为 英特尔 VTune 的一部分提供。虽然 Perf 是特定于 Linux 的,但 SEP 可以在 Linux、Windows 和 FreeBSD 上使用。

    LLVM 工具 llvm-profgen 可以转换 Perf 或 SEP 的输出。一个外部项目 AutoFDO 还提供了一个 create_llvm_prof 工具,该工具支持 Linux Perf 输出。

    使用 Perf 时

    $ perf record -b -e BR_INST_RETIRED.NEAR_TAKEN:uppp ./code
    

    如果上面的事件不可用,branches:u 可能就是下一个最佳选择。

    注意 -b 标志的使用。这告诉 Perf 使用最后一个分支记录 (LBR) 来记录调用链。虽然这并非严格要求,但它提供了更好的调用信息,从而提高了配置文件数据的准确性。

    使用 SEP 时

    $ sep -start -out code.tb7 -ec BR_INST_RETIRED.NEAR_TAKEN:precise=yes:pdir -lbr no_filter:usr -perf-script brstack -app ./code
    

    这将生成一个 code.perf.data.script 输出,可与 llvm-profgen--perfscript 输入选项一起使用。

  2. 将收集的配置文件数据转换为 LLVM 的样本配置文件格式。目前,这可以通过 AutoFDO 转换器 create_llvm_prof 来支持。构建并安装后,您可以使用以下命令将 perf.data 文件转换为 LLVM

    $ create_llvm_prof --binary=./code --out=code.prof
    

    这将读取 perf.data 和二进制文件 ./code,并将配置文件数据输出到 code.prof 中。请注意,如果您在没有 -b 标志的情况下运行了 perf,则在调用 create_llvm_prof 时需要使用 --use_lbr=false

    或者,LLVM 工具 llvm-profgen 也可用于生成 LLVM 样本配置文件

    $ llvm-profgen --binary=./code --output=code.prof --perfdata=perf.data
    

    使用 SEP 时,输出将采用与 llvm-profgen --perfscript 相对应的文本格式。例如

    $ llvm-profgen --binary=./code --output=code.prof --perfscript=code.perf.data.script
    
  3. 使用收集的配置文件再次构建代码。此步骤将配置文件反馈回优化器。这将生成一个比原始二进制文件执行速度更快的二进制文件。请注意,您不需要使用与第一步中使用的完全相同的参数来构建代码。唯一的要求是您使用相同的调试信息选项和 -fprofile-sample-use 来构建代码。

    在 Linux 上

    $ clang++ -O2 -gline-tables-only \
      -fdebug-info-for-profiling -funique-internal-linkage-names \
      -fprofile-sample-use=code.prof code.cc -o code
    

    在 Windows 上

    > clang-cl /O2 -gdwarf -gline-tables-only ^
      /clang:-fdebug-info-for-profiling /clang:-funique-internal-linkage-names ^
      /fprofile-sample-use=code.prof code.cc /Fe:code /fuse-ld=lld /link /debug:dwarf
    

    [可选] 基于采样的配置文件可能存在不准确或丢失的块/边缘计数器。配置文件推断算法 (profi) 可用于推断丢失的块和边缘计数,并提高配置文件数据的质量。使用 -fsample-profile-use-profi 启用它。例如,在 Linux 上

    $ clang++ -fsample-profile-use-profi -O2 -gline-tables-only \
      -fdebug-info-for-profiling -funique-internal-linkage-names \
      -fprofile-sample-use=code.prof code.cc -o code
    

    在 Windows 上

    > clang-cl /clang:-fsample-profile-use-profi /O2 -gdwarf -gline-tables-only ^
      /clang:-fdebug-info-for-profiling /clang:-funique-internal-linkage-names ^
      /fprofile-sample-use=code.prof code.cc /Fe:code /fuse-ld=lld /link /debug:dwarf
    
样本配置文件格式

由于外部分析器以各种自定义格式生成配置文件数据,因此分析器生成的数据必须转换为后端可以读取的格式。LLVM 支持三种不同的样本配置文件格式

  1. ASCII 文本。这是最容易生成的格式。文件分为几个部分,分别对应于具有配置文件信息的每个函数。格式如下所述。它也可以使用 llvm-profdata 工具从二进制或 gcov 格式生成。

  2. 二进制编码。这使用更有效的编码,可以产生更小的配置文件。这是 https://github.com/google/autofdo 中的 create_llvm_prof 工具生成的格式。

  3. GCC 编码。这基于 gcov 格式,GCC 可以接受该格式。它只在 GCC 和 Clang 同时存在的环境中才有用。这种编码只由 https://github.com/google/autofdo 中的 create_gcov 工具生成。LLVM 和 llvm-profdata 可以读取它,但它们都不能生成它。

如果您使用 Linux Perf 生成采样配置文件,则可以使用上一节中描述的转换工具 create_llvm_prof。否则,您需要编写一个转换工具,将您的分析器的本机格式转换为这三种格式之一。

样本配置文件文本格式

本节描述了采样配置文件的 ASCII 文本格式。可以说,它是最容易生成的格式。如果您有兴趣生成其他两种格式,请咨询 LLVM 源代码树中的 ProfileData 库(特别是 include/llvm/ProfileData/SampleProfReader.h)。

function1:total_samples:total_head_samples
 offset1[.discriminator]: number_of_samples [fn1:num fn2:num ... ]
 offset2[.discriminator]: number_of_samples [fn3:num fn4:num ... ]
 ...
 offsetN[.discriminator]: number_of_samples [fn5:num fn6:num ... ]
 offsetA[.discriminator]: fnA:num_of_total_samples
  offsetA1[.discriminator]: number_of_samples [fn7:num fn8:num ... ]
  offsetA1[.discriminator]: number_of_samples [fn9:num fn10:num ... ]
  offsetB[.discriminator]: fnB:num_of_total_samples
   offsetB1[.discriminator]: number_of_samples [fn11:num fn12:num ... ]

这是一个嵌套树,其中缩进表示内联堆栈的嵌套级别。文件中没有空行。一行内的空格是固定的。额外的空格会导致读取文件时出错。

任何以“#”字符开头的行将被完全忽略。

内联调用以缩进表示。内联堆栈是源位置的堆栈,其中堆栈的顶部表示叶子函数,堆栈的底部表示指令所属的实际符号。

函数名称必须经过混淆,以便配置文件加载器在当前翻译单元中匹配它们。函数头中的两个数字分别指定在函数中累积的总样本数(第一个数字)和在函数的序言中累积的总样本数(第二个数字)。此头部样本计数提供了函数调用频率的指标。

函数主体中有两种类型的行。

  • 采样行表示源位置的配置文件信息。 offsetN[.discriminator]: number_of_samples [fn5:num fn6:num ... ]

  • 调用点行表示内联调用点的配置文件信息。 offsetA[.discriminator]: fnA:num_of_total_samples

每个采样行可能包含多个项目。有些是可选的(如下所示)

  1. 源代码行偏移量。此数字表示收集样本的函数中的行号。行号始终相对于定义函数符号的行。因此,如果函数在第 280 行有头,则偏移量 13 在文件中的第 293 行。

    请注意,此偏移量永远不应该为负数。这可能在宏等情况下发生。调试机制将在宏展开时注册行号。因此,如果宏在函数开始之前的一行中展开,则配置文件转换器应将 0 作为偏移量输出(这意味着优化器将无法将有意义的权重与宏中的指令关联起来)。

  2. [可选] 鉴别器。如果采样程序使用 DWARF 鉴别器支持 (http://wiki.dwarfstd.org/index.php?title=Path_Discriminators) 编译,则使用它。DWARF 鉴别器是无符号整数,允许编译器区分同一源代码行位置上的多个执行路径。

    例如,考虑代码行 if (cond) foo(); else bar();。如果谓词 cond 80% 的时间为真,则进入函数 foo 的边应该被认为是大部分时间都在执行的。但对 foobar 的调用都在同一行,因此该行上的样本计数是不够的。编译器需要知道该行的哪一部分更频繁地执行。

    这是鉴别器提供的。在这种情况下,对 foobar 的调用将位于同一行,但将具有不同的鉴别器值。这允许编译器正确地将边权重设置到 foobar 中。

  3. 样本数量。这是一个整数,表示分析器在此源代码位置收集的样本数量。

  4. [可选]潜在调用目标和样本。如果存在,则此行包含调用指令。这模拟了直接调用和样本数量。例如,

    130: 7  foo:3  bar:2  baz:7
    

    以上表示在相对行偏移量 130 处存在一个调用指令,该指令调用 foo()bar()baz() 中的一个,其中 baz() 是相对调用频率更高的目标。

例如,考虑一个具有调用链 main -> foo -> bar 的程序。在启用优化的情况下构建时,编译器可能会将对 barfoo 的调用内联到 main 中。生成的概要文件可能如下所示

main:35504:0
1: _Z3foov:35504
  2: _Z32bari:31977
  1.1: 31977
2: 0

此概要文件表明在 main 中共收集了 35,504 个样本。所有这些样本都在第 1 行(对 foo 的调用)。其中,31,977 个样本在 bar 的主体内部使用。概要文件的最后一行 (2: 0) 对应于 main 中的第 2 行。那里没有收集到任何样本。

通过插桩进行分析

Clang 还支持通过插桩进行分析。这需要构建代码的特殊插桩版本,并且在分析期间具有一定的运行时开销,但它提供比采样分析器更详细的结果。它还提供可重现的结果,至少在代码在运行之间保持一致的程度上是可重现的。

Clang 支持两种类型的插桩:前端插桩和 IR 插桩。前端插桩可以通过选项 -fprofile-instr-generate 启用,而 IR 插桩可以通过选项 -fprofile-generate 启用。为了获得 PGO 的最佳性能,应使用基于 IR 的插桩。它具有插桩开销较低、原始概要文件大小较小和运行时性能更好的优势。另一方面,前端插桩具有更好的源代码关联性,因此应将其与基于源代码行的覆盖测试一起使用。

标志 -fcs-profile-generate 也使用与 -fprofile-generate 相同的插桩方法对程序进行插桩。但是,它执行内联后延迟插桩,并可以生成上下文敏感的概要文件。

以下是使用插桩进行概要文件引导优化步骤。

  1. 通过使用选项 -fprofile-generate-fprofile-instr-generate 编译和链接来构建代码的插桩版本。

    $ clang++ -O2 -fprofile-instr-generate code.cc -o code
    
  2. 使用反映典型用法的输入运行插桩的可执行文件。默认情况下,概要文件数据将写入当前目录中的 default.profraw 文件。您可以通过使用选项 -fprofile-instr-generate= 或通过设置 LLVM_PROFILE_FILE 环境变量来指定备用文件来覆盖该默认值。如果环境变量和命令行选项都指定了非默认文件名,则环境变量优先。指定的文件名模式可以包含不同的修饰符:%p%h%m%t%c

    该文件名中的任何 %p 实例都将被进程 ID 替换,以便您可以轻松区分来自多次运行的概要文件输出。

    $ LLVM_PROFILE_FILE="code-%p.profraw" ./code
    

    修饰符 %h 可用于相同插桩二进制文件在多个不同的主机上运行并将概要文件数据转储到基于共享网络的存储的场景中。 %h 规范符将被主机名替换,以便从不同主机收集的概要文件不会互相覆盖。

    虽然使用 %p 规范符可以降低从不同进程转储的概要文件相互覆盖的可能性,但这种覆盖仍然可能发生,因为操作系统会重新使用 pid。使用 %p 的另一个副作用是原始概要文件数据的存储要求大大增加。为了避免此类问题,可以在概要文件名称中使用 %m 规范符。当使用此规范符时,分析器运行时将使用与插桩二进制文件关联的唯一整数标识符替换 %m。此外,分析器运行时将在转储过程中自动合并来自共享文件系统(可以在不同主机上)的不同进程转储的多个原始概要文件。如果程序链接了多个插桩共享库,则每个库都会将其概要文件数据转储到其自己的概要文件数据文件(在概要文件名称中嵌入其唯一的整数 ID)。请注意, %m 启用的合并适用于分析器运行时生成的原始概要文件数据。生成的合并“原始”概要文件数据文件仍然需要转换为编译器期望的不同格式(请参见下面的步骤 3)。

    $ LLVM_PROFILE_FILE="code-%m.profraw" ./code
    

    有关 %t%c 修饰符的信息,请参见 此处 部分。

  3. 合并来自多次运行的概要文件,并将“原始”概要文件格式转换为 clang 预期输入的格式。使用 llvm-profdata 工具的 merge 命令来执行此操作。

    $ llvm-profdata merge -output=code.profdata code-*.profraw
    

    请注意,即使只有一个“原始”概要文件,此步骤也是必要的,因为合并操作也会更改文件格式。

  4. 使用选项 -fprofile-use-fprofile-instr-use 再次构建代码以指定收集的概要文件数据。

    $ clang++ -O2 -fprofile-instr-use=code.profdata code.cc -o code
    

    您可以根据需要重复步骤 4,而无需重新生成概要文件。当您对代码进行更改时,clang 可能无法再使用概要文件数据。如果发生这种情况,它会向您发出警告。

请注意, -fprofile-use 选项在语义上等效于其 GCC 对应项,它 *不* 处理 GCC 生成的概要文件格式。 -fprofile-use-fprofile-instr-use 都接受索引格式的概要文件,无论它是前端还是 IR 传递生成的。

-fprofile-generate[=<dirname>]

标志 -fprofile-generate-fprofile-generate= 将使用另一种插桩方法来生成概要文件。如果提供目录名,它将在指定的 dirname 目录中生成概要文件 default_%m.profraw。如果 dirname 不存在,它将在运行时创建。 %m 规范符将被上面步骤 2 中记录的唯一 ID 替换。换句话说,使用 -fprofile-generate[=<dirname>] 选项,默认情况下会打开“原始”概要文件数据的自动合并,因此不再存在来自不同运行进程的概要文件覆盖的风险。例如,

$ clang++ -O2 -fprofile-generate=yyy/zzz code.cc -o code

当执行 code 时,概要文件将写入文件 yyy/zzz/default_xxxx.profraw

要使用编译器可读格式生成概要文件数据文件,可以使用 llvm-profdata 工具,并将概要文件目录作为输入

$ llvm-profdata merge -output=code.profdata yyy/zzz/

如果用户想要关闭自动合并功能,或者只是覆盖命令行指定的概要文件转储路径,仍然可以使用环境变量 LLVM_PROFILE_FILE 在运行时覆盖概要文件文件的目录和文件名。要覆盖编译时的路径和文件名,请使用 -Xclang -fprofile-instrument-path=/path/to/file_pattern.profraw

-fcs-profile-generate[=<dirname>]

标志 -fcs-profile-generate-fcs-profile-generate= 将使用相同的插桩方法,并与 -fprofile-generate-fprofile-generate= 标志生成相同的概要文件。区别在于插桩是在内联之后执行的,因此生成的结果概要文件具有更好的上下文敏感信息。它们不能与 -fprofile-generate-fprofile-generate= 标志一起使用。它们通常与 -fprofile-use 标志结合使用。 -fcs-profile-generate-fprofile-generate 生成的概要文件可以通过 llvm-profdata 合并。使用示例

$ clang++ -O2 -fprofile-generate=yyy/zzz code.cc -o code
$ ./code
$ llvm-profdata merge -output=code.profdata yyy/zzz/

前几个步骤与 -fprofile-generate 编译中的步骤相同。然后执行第二轮插桩。

$ clang++ -O2 -fprofile-use=code.profdata -fcs-profile-generate=sss/ttt \
  -o cs_code
$ ./cs_code
$ llvm-profdata merge -output=cs_code.profdata sss/ttt code.profdata

生成的 cs_code.prodatacode.profdata 与从二进制文件 cs_code 生成的概要文件合并。概要文件 cs_code.profata 可以由 -fprofile-use 编译使用。

$ clang++ -O2 -fprofile-use=cs_code.profdata

以上命令将在插桩的相同点将两个概要文件都读入编译器。

-fprofile-use[=<pathname>]

如果没有其他参数,-fprofile-use 的行为与 -fprofile-instr-use 相同。否则,如果 pathname 是配置文件的完整路径,它将从该文件读取。如果 pathname 是目录名,它将从 pathname/default.profdata 读取。

-fprofile-update[=<method>]

除非指定了 -fsanitize=thread,否则默认值为 single,它使用非原子递增。在线程竞争的情况下,计数器可能不准确。 atomic 使用原子递增,这很准确,但有开销。 prefer-atomic 将在目标支持的情况下转换为 atomic,否则转换为 single

微调配置文件收集

PGO 基础设施提供了用户程序旋钮以微调配置文件收集。具体来说,PGO 运行时提供了以下函数,可用于控制程序中应收集配置文件的区域。

  • void __llvm_profile_set_filename(const char *Name):将配置文件的名称更改为 Name

  • void __llvm_profile_reset_counters(void):将所有计数器重置为零。

  • int __llvm_profile_dump(void):将配置文件数据写入磁盘。

  • int __llvm_orderfile_dump(void):将排序文件写入磁盘。

例如,以下模式可用于跳过配置文件初始化,配置文件两个特定热点区域,并跳过配置文件清理

int main() {
  initialize();

  // Reset all profile counters to 0 to omit profile collected during
  // initialize()'s execution.
  __llvm_profile_reset_counters();
  ... hot region 1
  // Dump the profile for hot region 1.
  __llvm_profile_set_filename("region1.profraw");
  __llvm_profile_dump();

  // Reset counters before proceeding to hot region 2.
  __llvm_profile_reset_counters();
  ... hot region 2
  // Dump the profile for hot region 2.
  __llvm_profile_set_filename("region2.profraw");
  __llvm_profile_dump();

  // Since the profile has been dumped, no further profile data
  // will be collected beyond the above __llvm_profile_dump().
  cleanup();
  return 0;
}

这些 API 的名称可以通过两种方式引入用户程序。它们可以在支持将弱符号在链接期间视为 null 的平台上声明为弱符号。例如,用户可以有

__attribute__((weak)) int __llvm_profile_dump(void);

// Then later in the same source file
if (__llvm_profile_dump)
  if (__llvm_profile_dump() != 0) { ... }
// The first if condition tests if the symbol is actually defined.
// Profile dumping only happens if the symbol is defined. Hence,
// the user program works correctly during normal (not profile-generate)
// executions.

或者,用户程序可以包含头文件 profile/instr_prof_interface.h,其中包含 API 名称。例如,

#include "profile/instr_prof_interface.h"

// Then later in the same source file
if (__llvm_profile_dump() != 0) { ... }

用户代码不需要检查 API 名称是否已定义,因为如果 clang 不是为配置文件生成进行编译,则这些名称将自动替换为 (0) 或 noop 的等效项。

这种替换可能发生,因为 clang 根据 -fprofile-generate-fprofile-use 标志添加了两个宏之一。

  • __LLVM_INSTR_PROFILE_GENERATE:当 -fprofile[-instr]-generate/-fcs-profile-generate 其中之一生效时定义。

  • __LLVM_INSTR_PROFILE_USE:当 -fprofile-use/-fprofile-instr-use 其中之一生效时定义。

这两个宏可用于提供更大的灵活性,以便用户程序可以执行专门用于配置文件生成或配置文件使用的代码。例如,用户程序在配置文件生成期间可以有特殊的日志记录

#if __LLVM_INSTR_PROFILE_GENERATE
expensive_logging_of_full_program_state();
#endif

在程序的正常构建期间,日志记录将自动排除,因此不会影响程序正常执行期间的性能。

建议仅在程序的冷区域使用这种微调。弱符号会引入额外的控制流(if 检查),而宏(因此它们在 profile/instr_prof_interface.h 中保护的声明)会更改使用它们的函数的控制流,在配置文件生成和配置文件使用之间(这可能导致此类函数中的计数器被丢弃)。在程序的冷区域使用这些 API 会引入更少的开销,并导致更优化的代码。

禁用检测

在某些情况下,在不影响用于项目中其他文件的其他主要编译标志的情况下,禁用特定文件的配置文件生成或使用可能很有用。

在这些情况下,您可以使用标志 -fno-profile-instr-generate(或 -fno-profile-generate)禁用配置文件生成,并使用 -fno-profile-instr-use(或 -fno-profile-use)禁用配置文件使用。

请注意,这些标志应出现在相应的配置文件标志之后才能生效。

提示

当二进制文件内部的任何翻译单元都没有被检测到时,在 Fuchsia 的情况下,配置文件运行时将不会链接到二进制文件中,并且不会生成配置文件,而在其他平台上,配置文件运行时将被链接,并且配置文件将被生成,但不会有任何计数器。

仅检测选定文件或函数

有时仅检测某些文件或函数很有用。例如,在自动化测试基础结构中,可能希望仅检测由补丁修改的文件或函数,以减少检测整个系统的开销。

这可以使用 -fprofile-list 选项来完成。

-fprofile-list=<pathname>

此选项可用于仅对选定的文件或函数应用配置文件检测。 pathname 应指向 Sanitizer 特殊情况列表 格式的文件,该文件选择要检测的文件和函数。

$ clang++ -O2 -fprofile-instr-generate -fprofile-list=fun.list code.cc -o code

可以多次指定该选项以传递多个文件。

$ clang++ -O2 -fprofile-instr-generate -fcoverage-mapping -fprofile-list=fun.list -fprofile-list=code.list code.cc -o code

支持的部分是 [clang][llvm][csllvm],分别代表 clang PGO、IRPGO 和 CSIRPGO。支持的前缀是 functionsource。支持的类别是 allowskipforbidskip 添加 skipprofile 属性,而 forbid 添加 noprofile 属性到适当的函数。使用 default:<allow|skip|forbid> 指定默认类别。

$ cat fun.list
# The following cases are for clang instrumentation.
[clang]

# We might not want to profile functions that are inlined in many places.
function:inlinedLots=skip

# We want to forbid profiling where it might be dangerous.
source:lib/unsafe/*.cc=forbid

# Otherwise we allow profiling.
default:allow
较旧的前缀

还支持较旧的格式,但它只能添加 noprofile 属性。要过滤单个函数或整个源文件,请分别使用 fun:<name>src:<file>。要排除函数或源文件,请分别使用 !fun:<name>!src:<file>。该格式还支持通配符扩展。假设编译器生成的函数位于主源文件中。也可以通过使用命名部分来限制过滤器到特定的检测类型。

# all functions whose name starts with foo will be instrumented.
fun:foo*

# except for foo1 which will be excluded from instrumentation.
!fun:foo1

# every function in path/to/foo.cc will be instrumented.
src:path/to/foo.cc

# bar will be instrumented only when using backend instrumentation.
# Recognized section names are clang, llvm and csllvm.
[llvm]
fun:bar

当文件仅包含排除项时,除了排除的项之外的所有文件和函数都将被检测。否则,将仅检测指定的文件和函数。

检测函数组

有时希望最大限度地减少被检测二进制文件的大小开销。一种方法是将函数划分为组,并仅检测指定组中的函数。这可以使用 -fprofile-function-groups-fprofile-selected-function-group 选项来完成。

-fprofile-function-groups=<N>, -fprofile-selected-function-group=<i>

以下使用 3 个组

$ clang++ -Oz -fprofile-generate=group_0/ -fprofile-function-groups=3 -fprofile-selected-function-group=0 code.cc -o code.0
$ clang++ -Oz -fprofile-generate=group_1/ -fprofile-function-groups=3 -fprofile-selected-function-group=1 code.cc -o code.1
$ clang++ -Oz -fprofile-generate=group_2/ -fprofile-function-groups=3 -fprofile-selected-function-group=2 code.cc -o code.2

在从三个二进制文件收集原始配置文件后,它们可以像往常一样合并到一个配置文件中。

$ llvm-profdata merge -output=code.profdata group_*/*.profraw

配置文件重新映射

当在影响许多符号名称的更改后编译程序时,预先存在的配置文件数据可能不再与程序匹配。例如

  • 从 libstdc++ 切换到 libc++ 将导致所有采用标准库类型的函数的修饰名称发生更改

  • 重命名 C++ 中广泛使用的类型将导致所有具有涉及该类型的参数的函数的修饰名称发生更改

  • 从 32 位编译迁移到 64 位编译可能会更改 size_t 和类似类型的底层类型,从而导致修饰发生变化

Clang 允许使用配置文件重新映射文件来指定在将配置文件数据与程序匹配时应忽略此类修饰名称差异。

-fprofile-remapping-file=<file>

指定一个包含配置文件重新映射信息的文件,该文件将用于将配置文件数据中的修饰名称匹配到程序中的修饰名称。

配置文件重新映射文件是一个文本文件,包含以下形式的行

fragmentkind fragment1 fragment2

其中 fragmentkindnametypeencoding 之一,分别表示以下混淆名称片段是 <名称>、<类型> 还是 <编码>。空行和以 # 开头的行将被忽略。

为方便起见,诸如 StSs 的内置 <替换> 被接受为 <名称>(即使它们在技术上并非 <名称>)。

例如,要指定在匹配配置文件数据时应将 absl::string_viewstd::string_view 视为等效,可以使用以下重新映射文件

# absl::string_view is considered equivalent to std::string_view
type N4absl11string_viewE St17basic_string_viewIcSt11char_traitsIcEE

# std:: might be std::__1:: in libc++ or std::__cxx11:: in libstdc++
name 3std St3__1
name 3std St7__cxx11

使用配置文件重新映射文件匹配配置文件数据以尽力而为的方式进行支持。例如,目前不会重新映射有关间接调用目标的信息。为了获得最佳效果,建议生成匹配更新程序的新配置文件数据,或使用 llvm-cxxmapllvm-profdata merge 工具重新映射配置文件数据。

提示

配置文件数据重新映射目前仅支持遵循 Itanium C++ ABI 混淆方案的 C++ 混淆名称。这涵盖了 Clang 支持的所有 C++ 目标,除了 Windows。

基于 GCOV 的分析

GCOV 是一个测试覆盖率程序,它可以帮助您了解代码行的执行频率。使用 --coverage 选项对代码进行插桩时,会在连接基本块的每个边添加一些计数器。

在编译时,会生成包含有关块和它们之间边的信息的 gcno 文件。在运行时,会递增计数器,并在退出时,会将计数器转储到 gcda 文件中。

工具 llvm-cov gcov 将解析 gcno、gcda 和源文件以生成报告 .c.gcov

-fprofile-filter-files=[regexes]

定义用分号分隔的正则表达式列表。如果文件名与任何正则表达式匹配,则对该文件进行插桩。

$ clang --coverage -fprofile-filter-files=".*\.c$" foo.c

例如,这将只对以 .c 结尾的文件进行插桩,跳过 .h 文件。

-fprofile-exclude-files=[regexes]

定义用分号分隔的正则表达式列表。如果文件名不与所有正则表达式匹配,则对该文件进行插桩。

$ clang --coverage -fprofile-exclude-files="^/usr/include/.*$" foo.c

例如,这将对除 /usr/include 中的文件之外的所有文件进行插桩。

如果同时使用这两个选项,则如果文件名称与来自 -fprofile-filter-list 的任何正则表达式匹配,并且不与来自 -fprofile-exclude-list 的所有正则表达式匹配,则对该文件进行插桩。

$ clang --coverage -fprofile-exclude-files="^/usr/include/.*$" \
        -fprofile-filter-files="^/usr/.*$"

在这种情况下,/usr/foo/oof.h 将被插桩,因为它与过滤器正则表达式匹配,并且不与排除正则表达式匹配,但 /usr/include/foo.h 不会,因为它与排除正则表达式匹配。

控制调试信息

控制调试信息的尺寸

可以通过下面列出的标志之一设置 Clang 生成的调试信息类型。如果存在多个标志,则使用最后一个标志。

-g0

不生成任何调试信息(默认值)。

-gline-tables-only

仅生成行号表。

这种调试信息允许使用函数名称、文件名和行号(通过诸如 gdbaddr2line 之类的工具)来获取堆栈跟踪。它不包含任何其他数据(例如,局部变量或函数参数的描述)。

-fstandalone-debug

Clang 支持许多优化,以减小二进制文件中调试信息的尺寸。它们基于以下假设而工作:调试类型信息可以分散在多个编译单元中。具体而言,优化是

  • 不会为模块不需要的类型发出类型定义,这些类型可以用前向声明替换。

  • 只会为包含类 vtable 的模块中的动态 C++ 类发出类型信息。

  • 只会为包含其构造函数之一定义的模块中的 C++ 类(非平凡的、非聚合的)发出类型信息。

  • 只会为在存在类型的显式实例化定义的情况下是显式模板实例化声明主题的类型发出类型定义。

-fstandalone-debug 选项会关闭这些优化。当使用没有调试信息的第三方库时,这很有用。请注意,Clang 永远不会为程序完全未引用的类型发出类型信息。

-fno-standalone-debug

在 Darwin 上,默认情况下启用 -fstandalone-debug-fno-standalone-debug 选项可用于打开上面描述的基于 vtable 的优化。

-g

生成完整的调试信息。

-feliminate-unused-debug-types

默认情况下,Clang 不会为在程序中定义但未使用的类型发出类型信息。要保留这些未用类型的调试信息,可以使用否定 -fno-eliminate-unused-debug-types。这在 Windows 上特别有用,当使用可以引用否则会被剥离的 const 符号的 NATVIS 文件时,即使在完全调试或独立调试模式下也是如此。

控制宏调试信息生成

C 预处理器宏的调试信息会增加二进制文件中调试信息的尺寸。可以通过下面列出的标志控制 Clang 生成的宏调试信息。

-fdebug-macro

为预处理器宏生成调试信息。当启用 -g0 时,此标志将被丢弃。

-fno-debug-macro

不为预处理器宏生成调试信息(默认值)。

控制调试器“调整”

虽然 Clang 通常会发出标准的 DWARF 调试信息 (http://dwarfstd.org),但不同的调试器可能知道如何利用不同的特定 DWARF 功能。您可以为几个不同的调试器之一“调整”调试信息。

-ggdb, -glldb, -gsce, -gdbx

分别为 gdblldb、Sony PlayStation® 调试器或 dbx 调整调试信息。这些选项中的每一个都隐含 -g。(因此,如果您想要 -gline-tables-only 和调试器调整,则调整选项必须放在前面。)

控制 LLVM IR 输出

控制 LLVM IR 中的值名称

在 LLVM IR 中发出值名称会增加 IR 的尺寸和冗长性。默认情况下,值名称仅在启用断言的 Clang 版本中发出。但是,在读取 IR 时,重新启用值名称的发出以提高可读性可能会有所帮助。

-fdiscard-value-names

在生成 LLVM IR 时丢弃值名称。

-fno-discard-value-names

在生成 LLVM IR 时不要丢弃值名称。此选项可用于为 Clang 的发布版本重新启用名称。

注释解析选项

Clang 解析 Doxygen 和非 Doxygen 样式的文档注释,并将它们附加到相应的声明节点。默认情况下,它只解析 Doxygen 样式的注释,并忽略以 ///* 开头的普通注释。

-Wdocumentation

发出有关使用文档注释的警告。此警告组默认情况下处于关闭状态。

这包括检查 \param 命令是否命名函数签名中实际存在的参数,检查 \returns 是否仅用于实际返回值的函数等。

-Wno-documentation-unknown-command

遇到未知的 Doxygen 命令时不要发出警告。

-fparse-all-comments

将所有注释解析为文档注释(包括以 ///* 开头的普通注释)。

-fcomment-block-commands=[commands]

将自定义文档命令定义为块命令。这允许 Clang 为这些自定义命令构建正确的 AST,并消除关于未知命令的警告。多个命令必须用逗号分隔,并且 *不带尾随空格*;例如 -fcomment-block-commands=foo,bar 定义自定义命令 \foo\bar

也可以多次使用 -fcomment-block-commands;例如 -fcomment-block-commands=foo -fcomment-block-commands=bar 与上面相同。

C 语言特性

clang 对标准 C 的支持功能齐全,除了 C99 浮点pragma。

clang 支持的扩展

参见 Clang 语言扩展

各种标准模式之间的差异

clang 支持 -std 选项,该选项会更改 clang 使用的语言模式。C 支持的模式为 c89、gnu89、c94、c99、gnu99、c11、gnu11、c17、gnu17、c23、gnu23、c2y、gnu2y 以及这些模式的各种别名。如果没有指定 -std 选项,clang 默认使用 gnu17 模式。许多 C99 和 C11 特性在较早的模式中作为符合扩展得到支持,并带有警告。使用 -pedantic-errors 在较早的模式中使用来自更高标准修订版的特性时请求错误。

所有 c*gnu* 模式之间的差异

  • c* 模式定义“__STRICT_ANSI__”。

  • gnu* 模式中定义了没有下划线前缀的目标特定定义,例如 linux

  • 三字母词在 gnu* 模式中默认关闭;可以使用 -trigraphs 选项启用它们。

  • 解析器在 gnu* 模式中将 asmtypeof 识别为关键字;变体 __asm____typeof__ 在所有模式中都被识别。

  • 解析器在 gnu* 模式中将 inline 识别为关键字,此外还将其识别为 *99 及更高版本模式中的关键字,这些模式是 ISO C 标准的一部分。变体 __inline__ 在所有模式中都被识别。

  • Apple 的“块”扩展在某些平台上的 gnu* 模式中默认被识别;它可以使用 -fblocks 选项在任何模式中启用。

*89*94 模式之间的差异

  • c89 模式不识别二字母词。

*94*99 模式之间的差异

  • *99 模式默认按 C99 中指定的实现 inline / __inline__,而 *89 模式实现 GNU 版本。可以使用 __gnu_inline__ 属性为单个函数覆盖此行为。

  • forifswitchwhiledo 语句内部定义的名称范围不同。(例如:if ((struct x {int x;}*)0) {}。)

  • __STDC_VERSION__ 未在 *89 模式中定义。

  • inline 在 c89 模式中不被识别为关键字。

  • restrict*89 模式中不被识别为关键字。

  • *99 模式允许在整数常量表达式中使用逗号。

  • *89 模式中,不是左值的数组不会被隐式提升为指针。

  • 某些警告不同。

*99*11 模式之间的差异

  • 使用 C11 特性的警告被禁用。

  • __STDC_VERSION__ 被定义为 201112L 而不是 199901L

*11*17 模式之间的差异

  • __STDC_VERSION__ 被定义为 201710L 而不是 201112L

*17*23 模式之间的差异

  • __STDC_VERSION__ 被定义为 202311L 而不是 201710L

  • nullptrnullptr_t 被支持,仅在 *23 模式中。

  • ATOMIC_VAR_INIT 已从 *23 模式中删除。

  • booltruefalsealignasalignofstatic_assertthread_local 现在是一级关键字,仅在 *23 模式中。

  • typeoftypeof_unqual 被支持,仅 *23 模式。

  • 位精确整数 (_BitInt(N)) 在 *23 模式中默认支持,在 *17 及更早版本中作为扩展支持。

  • [[]] 属性在 *23 模式中默认支持,在 *17 及更早版本中作为扩展支持。

*23*2y 模式之间的差异

  • __STDC_VERSION__ 被定义为 202400L 而不是 202311L

尚未实现的 GCC 扩展

clang 尽可能地与 gcc 兼容,但某些 gcc 扩展尚未实现。

  • clang 尚未支持十进制浮点类型 (_Decimal32 及其相关类型)。

  • clang 不支持嵌套函数;这是一个复杂的功能,使用频率很低,因此不太可能在短期内实现。在 C++11 中,可以通过将 lambda 函数分配给局部变量来模拟它,例如

    auto const local_function = [&](int parameter) {
      // Do something
    };
    ...
    local_function(1);
    
  • clang 仅在指定寄存器不可分配时(例如,堆栈指针)才支持全局寄存器变量。对通用全局寄存器变量的支持不太可能很快实现,因为它需要额外的 LLVM 后端支持。

  • clang 不支持灵活数组成员的静态初始化。这似乎是一个很少使用的扩展,但在用户需求的推动下可以实现。

  • clang 不支持 __builtin_va_arg_pack/__builtin_va_arg_pack_len。这很少使用,但在某些可能有趣的地方,例如 glibc 标头,因此在用户需求的推动下可能会实现。请注意,由于 clang 假装像 GCC 4.2 一样,而此扩展是在 4.3 中引入的,因此 glibc 标头目前不会尝试在 clang 中使用此扩展。

  • clang 不支持 gcc 用于向前声明函数参数的扩展;但这尚未在任何实际代码中出现,因此可能永远不会实现。

这不是一个完整的列表;如果您发现此列表中缺少不支持的扩展,请发送电子邮件至 cfe-dev。此列表目前不包括 C++;参见 C++ 语言特性。此外,此列表不包括主要实现的功能中的错误;有关已知现有错误,请参见 错误跟踪器(FIXME:是否存在关于错误报告指南的部分?)。

有意不支持的 GCC 扩展

  • clang 不支持 GCC 扩展,该扩展允许在结构体中使用可变长数组。这是由于以下几个原因:第一,实现起来很复杂;第二,该扩展完全没有文档记录;第三,该扩展似乎很少使用。请注意,clang 确实 支持灵活数组成员(结构体末尾具有零长度或未指定长度的数组)。

  • GCC 接受许多表达式形式,这些形式在位字段宽度、枚举常量、case 标签以及全局范围内的数组边界中不是有效的整型常量表达式。clang 在这些上下文中也接受额外的表达式形式,但 GCC 在解析过程中执行简化操作而接受的结构(例如 x - x(其中 x 是一个变量))可能永远不会被 clang 接受。

  • clang 不支持 __builtin_apply 及其相关函数;此扩展极其晦涩难懂,难以可靠地实现。

Microsoft 扩展

clang 支持 Microsoft Visual C++ 中的许多扩展。要启用这些扩展,请使用 -fms-extensions 命令行选项。这是 Windows 目标的默认选项。clang 并非实现 MSVC 提供的每个 pragma 或 declspec,但它很好地支持了一些常用的 pragma 和 declspec,例如 __declspec(dllexport)#pragma comment(lib)

clang 具有一个 -fms-compatibility 标志,它使 clang 能够接受足够多的无效 C++ 代码,从而解析大多数 Microsoft 头文件。例如,它允许 对依赖基类成员进行非限定查找,这是 clang 的常见兼容性问题。此标志在 Windows 目标上默认启用。

-fdelayed-template-parsing 允许 clang 将函数模板定义的解析推迟到翻译单元的末尾。此标志在 Windows 目标上默认启用。

为了与使用 MSVC 编译的现有代码兼容,clang 定义了 _MSC_VER_MSC_FULL_VER 宏。在 Windows 上,这些宏的默认值与当前安装的 cl.exe 版本的值相同,或者分别为 1933193300000-fms-compatibility-version= 标志会覆盖这些值。它接受一个带点的版本元组,例如 19.00.23506。更改 MSVC 兼容性版本将使 clang 的行为更像该版本的 MSVC。例如, -fms-compatibility-version=19 将启用 C++14 功能并定义 char16_tchar32_t 为内置类型。

C++ 语言特性

clang 完全实现了标准 C++98(除了在 C++11 中删除的导出模板),所有标准 C++11、C++14 和 C++17,以及大部分 C++20。

请参阅 Clang 中的 C++ 支持 页面,以获取有关跨 Clang 版本的 C++ 特性支持的详细信息。

控制实现限制

-fbracket-depth=N

将嵌套括号、方括号和大括号的限制设置为 N。默认值为 256。

-fconstexpr-depth=N

将 constexpr 函数调用的限制设置为 N。默认值为 512。

-fconstexpr-steps=N

将单个常量表达式求值中求值的完整表达式的数量限制设置为 N。这也控制了可以常量求值的数组和动态数组分配的最大大小。默认值为 1048576。

-ftemplate-depth=N

将递归嵌套模板实例化的限制设置为 N。默认值为 1024。

-foperator-arrow-depth=N

将对 ‘operator->’ 函数的迭代调用的限制设置为 N。默认值为 256。

Objective-C 语言特性

Objective-C++ 语言特性

OpenMP 特性

clang 支持所有 OpenMP 4.5 指令和子句。有关更多详细信息,请参阅 OpenMP 支持

使用 -fopenmp 启用 OpenMP。可以使用 -fno-openmp 禁用对 OpenMP 的支持。

使用 -fopenmp-simd 仅启用 OpenMP simd 功能,而不链接运行时库;对于组合结构(例如 #pragma omp parallel for simd),非 simd 指令和子句将被忽略。可以使用 -fno-openmp-simd 禁用此选项。

控制实现限制

-fopenmp-use-tls

控制 OpenMP 线程私有变量的代码生成。如果存在此选项,所有线程私有变量都将像线程局部变量一样生成,使用 TLS 支持。如果提供了 -fno-openmp-use-tls 或目标不支持 TLS,则线程私有变量的代码生成将依赖于 OpenMP 运行时库。

OpenCL 特性

clang 可用于编译 OpenCL 内核以在设备(例如 GPU)上执行。可以将内核编译为二进制文件(例如 AMDGPU),该文件可以上传到设备上直接运行(例如,使用 clCreateProgramWithBinary),或者可以编译为可以加载到其他工具链中的通用位码文件。

可以使用安装中的默认目标编译为二进制文件,方法如下

$ echo "kernel void k(){}" > test.cl
$ clang test.cl

可以为特定目标编译,方法是指定与目标相对应的三元组,例如

$ clang --target=nvptx64-unknown-unknown test.cl
$ clang --target=amdgcn-amd-amdhsa -mcpu=gfx900 test.cl

可以使用以下方法编译为位码

$ clang -c -emit-llvm test.cl

这将生成一个名为 test.bc 的文件,该文件可用于供应商工具链执行机器码生成。

请注意,如果编译为适用于 SPIR/SPIR-V 等通用目标的位码,则会生成可移植 IR,该 IR 可用于各种供应商工具以及开源工具(例如 SPIRV-LLVM Translator)以生成 SPIR-V 二进制文件。有关更多详细信息,请参阅 使用开源工具从 OpenCL 内核源代码离线编译到 SPIR-V。从 clang 14 开始,可以像 SPIR-V 支持部分 中所述的那样直接生成 SPIR-V。

clang 目前支持 OpenCL C 语言标准,最高支持 v2.0。clang 主要支持完整配置文件。嵌入配置文件的支持非常有限。从 clang 9 开始,OpenCL 现在提供了一种 C++ 模式(请参阅 OpenCL 的 C++)。

OpenCL v3.0 支持已完成,但它仍处于实验阶段,有关实验功能和限制的更多详细信息,请参阅 OpenCL 支持 页面。

OpenCL 特定选项

规范 v2.0 第 5.8.4 节 中的许多 OpenCL 构建选项都可用。

示例

$ clang -cl-std=CL2.0 -cl-single-precision-constant test.cl

用于 C 源代码编译的许多标志也可以在为 OpenCL 编译时传递,例如:-c-O<1-4|s>-o-emit-llvm 等。

一些额外的选项可用于支持特殊的 OpenCL 功能。

-cl-no-stdinc

允许禁用所有非编译器本机的额外类型和函数。这可能会略微降低编译速度,但许多来自 OpenCL 标准的声明将无法访问。例如,以下编译将失败。

$ echo "bool is_wg_uniform(int i){return get_enqueued_local_size(i)==get_local_size(i);}" > test.cl
$ clang -cl-std=CL2.0 -cl-no-stdinc test.cl
error: use of undeclared identifier 'get_enqueued_local_size'
error: use of undeclared identifier 'get_local_size'

有关标准类型和函数的更多信息,请参阅 关于 OpenCL 头文件的章节

-cl-ext

启用/禁用 OpenCL 扩展和可选功能的支持。所有 OpenCL 目标都会设置一个列表,其中包含它们支持的扩展。clang 允许使用 -cl-ext 标志用逗号分隔的扩展列表修改此列表,这些扩展以 '+''-' 为前缀。语法:-cl-ext=<(['-'|'+']<extension>[,])+>,其中扩展可以是 已发布的 OpenCL 扩展 之一,也可以是任何供应商扩展。或者,可以使用 'all' 启用或禁用所有已知扩展。

禁用 64 位 SPIR-V 目标的双精度支持的示例

$ clang -c --target=spirv64 -cl-ext=-cl_khr_fp64 test.cl

可以使用以下方法在 R600 AMD GPU 中启用除双精度支持以外的所有扩展

$ clang --target=r600 -cl-ext=-all,+cl_khr_fp16 test.cl

请注意,某些通用目标(例如 SPIR/SPIR-V)在 clang 中默认启用所有扩展/功能。

OpenCL 目标

OpenCL 目标源于常规 Clang 目标类。OpenCL 目标表示中的特定部分提供地址空间映射以及一组支持的扩展。

特定目标

有一组具体的硬件架构,OpenCL 可以针对这些架构进行编译。

  • 对于 AMD 目标

    $ clang --target=amdgcn-amd-amdhsa -mcpu=gfx900 test.cl
    
  • 对于 Nvidia 架构

    $ clang --target=nvptx64-unknown-unknown test.cl
    

通用目标

  • 可以针对 32 位或 64 位目标生成 SPIR-V 二进制文件。

    $ clang --target=spirv32 -c test.cl
    $ clang --target=spirv64 -c test.cl
    

    更多详细信息可以在 SPIR-V 支持部分 中找到。

  • SPIR 可用作通用目标,以允许生成可移植的位码,该位码可在跨 GPU 工具链中使用。该实现遵循 SPIR 规范。有两种版本可用于 32 位和 64 位。

    $ clang --target=spir test.cl -emit-llvm -c
    $ clang --target=spir64 test.cl -emit-llvm -c
    

    Clang 将为 OpenCL 版本 2.0 及更低版本生成与 SPIR v1.2 兼容的 IR,为 OpenCL v2.0 或 C++ 为 OpenCL 生成 SPIR v2.0。

  • x86 被一些与 x86 兼容的实现使用,并且目前保留用于向后兼容性(与早期实现(在 SPIR 目标支持之前)。对于“非 SPMD”目标,这些目标无法使用硬件动态生成多个工作项,这涵盖了几乎所有非 GPU 设备(例如 CPU 和 DSP),内核需要额外的处理才能支持多个工作项执行。为此,可以使用第三方工具链,例如 POCL

    此目标不支持多个内存段,因此,可以使用 -ffake-address-space-map 标志添加伪地址空间映射。

    所有已知的 OpenCL 扩展和功能都设置为在通用目标中受支持,但是 -cl-ext 标志可用于切换单个扩展和功能。

OpenCL 标头

默认情况下,Clang 将包含标准标头,因此大多数 OpenCL 内置函数和类型在编译期间可用。可以使用标志 -cl-no-stdinc 禁用非本机编译器类型和函数的默认声明。

以下示例演示了 OpenCL 内核源代码,其中包含各种标准内置函数,可以在无需显式包含或编译器标志的情况下进行编译。

$ echo "bool is_wg_uniform(int i){return get_enqueued_local_size(i)==get_local_size(i);}" > test.cl
$ clang -cl-std=CL2.0 test.cl

有关默认标头的更多信息,请参阅 OpenCL 支持

OpenCL 扩展

大多数来自 官方 OpenCL 注册表 的 OpenCL C 的 cl_khr_* 扩展都可用,并根据特定架构中可用的支持情况在每个目标上进行配置。

可以使用 -cl-ext 标志更改每个目标的默认扩展设置。(有关更多详细信息,请参阅 标志说明)。

供应商扩展可以通过声明与每个扩展相关的类型和函数列表(包含在以下编译器 pragma 指令中)来灵活地添加。

#pragma OPENCL EXTENSION the_new_extension_name : begin
// declare types and functions associated with the extension here
#pragma OPENCL EXTENSION the_new_extension_name : end

例如,解析以下代码将 my_t 类型和 my_func 函数添加到自定义 my_ext 扩展中。

#pragma OPENCL EXTENSION my_ext : begin
typedef struct{
  int a;
}my_t;
void my_func(my_t);
#pragma OPENCL EXTENSION my_ext : end

扩展之间没有标识符冲突解决。因此,建议在标识符前加上双下划线以避免与用户空间标识符冲突。供应商扩展应使用保留的标识符前缀,例如 amd、arm、intel。

Clang 还支持在 OpenCL C 语言扩展文档 中记录的语言扩展。

OpenCL 特定属性

Clang 中的 OpenCL 支持包含一组直接从规范中获取的属性以及其他属性。

另请参阅 Clang 中的属性

nosvm

Clang 支持此属性以符合 OpenCL v2.0 标准,但它对 IR 没有影响。有关更多详细信息,请参阅规范 第 6.7.2 节

opencl_unroll_hint

此功能的实现反映了 C 的展开提示。有关语法的更多详细信息,请参阅规范 第 6.11.5 节

convergent

为了确保单程序多数据 (SPMD) / 单指令多线程 (SIMT) Clang 不会发生无效的优化,它提供了属性,这些属性可用于具有跨工作项语义的特殊函数。一个例子是子组操作,例如 intel_sub_group_shuffle

// Define custom my_sub_group_shuffle(data, c)
// that makes use of intel_sub_group_shuffle
r1 = ...
if (r0) r1 = computeA();
// Shuffle data from r1 into r3
// of threads id r2.
r3 = my_sub_group_shuffle(r1, r2);
if (r0) r3 = computeB();

对于非 SPMD 语义,这将被优化为以下等效代码

r1 = ...
if (!r0)
  // Incorrect functionality! The data in r1
  // have not been computed by all threads yet.
  r3 = my_sub_group_shuffle(r1, r2);
else {
  r1 = computeA();
  r3 = my_sub_group_shuffle(r1, r2);
  r3 = computeB();
}

用 convergent 属性声明函数 my_sub_group_shuffle 将阻止这种情况

my_sub_group_shuffle() __attribute__((convergent));

使用 convergent 可以通过保持 CFG 等效性(相对于标记为 convergent 的操作)来保证正确执行。CFG 等效于 G wrt 节点 Niiff Nj (i≠j) 相对于 Ni 的支配和后支配关系在 G 中保持相同。

noduplicate

noduplicate 在优化方面比 convergent 更加严格,因为 convergent 函数仅保留 CFG 等效性。这允许进行一些优化,只要控制流保持不变。

for (int i=0; i<4; i++)
  my_sub_group_shuffle()

可以修改为

my_sub_group_shuffle();
my_sub_group_shuffle();
my_sub_group_shuffle();
my_sub_group_shuffle();

而使用 noduplicate 将不允许这样做。此外,noduplicate 并不像 convergent 那样具有相同的 CFG 安全语义,并且会导致修改原始程序语义的 CFG 更改。

noduplicate 仅保留用于向后兼容性,并且对于未来的使用,它被认为已弃用。

C++ for OpenCL

从 clang 9 开始,内核代码可以包含 C++17 功能:类、模板、函数重载、类型推断等。请注意,这不是 OpenCL C++ 的实现,并且在 clang 中未来任何新版本中都没有计划支持它。

Clang 目前支持 OpenCL 1.0 和 2021 的 C++。有关此语言的详细信息,请参阅 OpenCL 编程语言文档的 C++ 版本,该文档可在 最新版本官方版本 中找到。

要启用 OpenCL 模式下的 C++,请在编译 .clcpp 文件时传递以下命令行选项之一。

  • OpenCL 1.0 的 C++:-cl-std=clc++-cl-std=CLC++-cl-std=clc++1.0-cl-std=CLC++1.0-std=clc++-std=CLC++-std=clc++1.0-std=CLC++1.0

  • OpenCL 2021 的 C++:-cl-std=clc++2021-cl-std=CLC++2021-std=clc++2021-std=CLC++2021

使用示例
template<class T> T add( T x, T y )
{
  return x + y;
}

__kernel void test( __global float* a, __global float* b)
{
  auto index = get_global_id(0);
  a[index] = add(b[index], b[index+1]);
}
clang -cl-std=clc++1.0 test.clcpp
clang -cl-std=clc++ -c --target=spirv64 test.cl

默认情况下,扩展名为 .clcpp 的文件将使用 OpenCL 1.0 模式的 C++ 进行编译。

clang test.clcpp

为了向后兼容性,扩展名为 .cl 的文件也可以在 OpenCL 模式下使用 C++ 进行编译,但必须使用标志激活所需的语言模式。

clang -cl-std=clc++ test.cl

OpenCL 2021 的 C++ 支持目前处于实验阶段,有关更多详细信息,请参阅 OpenCL 支持

OpenCL 内核源代码的 C++ 也可以在线编译,在支持 cl_ext_cxx_for_opencl 扩展的驱动程序中编译。

构造和销毁全局对象

具有非平凡构造函数的全局对象需要在执行第一个使用全局对象的内核之前运行构造函数。类似地,具有非平凡析构函数的全局对象需要在执行使用程序对象的最后一个内核之后立即调用析构函数。在 OpenCL v2.2 之前的版本中,不支持调用全局构造函数。但是,一个简单的解决方法是手动将构造函数初始化内核排队,该内核具有以下命名方案 _GLOBAL__sub_I_<compiled file name>。此内核仅在编译后的二进制文件中存在具有非平凡构造函数的全局对象时才存在。一种检查方法是将 CL_PROGRAM_KERNEL_NAMES 传递给 clGetProgramInfo(OpenCL v2.0 s5.8.7),然后检查是否有任何内核名称与上述全局构造函数初始化内核的命名方案匹配。

请注意,如果将多个文件编译并链接到库中,则必须调用多个内核来初始化多个模块的全局对象。

目前,应用程序需要在运行任何使用这些对象的内核之前,手动运行全局对象的初始化。

clang -cl-std=clc++ test.cl

如果有任何要初始化的全局对象,最终的二进制文件将包含要入队的 _GLOBAL__sub_I_test.cl 内核。

请注意,手动解决方法仅适用于在程序范围内声明的对象。对于在函数内部使用非平凡构造函数的静态对象的构造,没有手动解决方法。

在 OpenCL v2.0 驱动程序中无法手动调用全局析构函数。但是,在 clReleaseProgram 上应释放用于程序范围对象的全部内存。

OpenCL 支持 页面描述了 OpenCL 的 C++ 标准库的有限实验性支持。

目标特定特性和限制

CPU 架构特性和限制

X86

对于 X86(32 位和 64 位)的支持在 Darwin(macOS)、Linux、FreeBSD 和 Dragonfly BSD 上被认为是稳定的:它已被测试可以正确编译许多大型 C、C++、Objective-C 和 Objective-C++ 代码库。

x86_64-mingw32 上,传递 i128(按值)与 Microsoft x64 调用约定不兼容。您可能需要调整 WinX86_64ABIInfo::classify()(位于 lib/CodeGen/Targets/X86.cpp 中)。

对于 X86 目标,clang 支持 -m16 命令行参数,该参数启用 16 位代码输出。这与使用 GNU 工具链中的 asm(".code16gcc") 大致类似。生成的代码和 ABI 保持为 32 位,但汇编器会发出适合在 16 位模式下运行的 CPU 的指令,并使用地址大小和操作数大小前缀来启用 32 位寻址和操作。

x86-64 psABI 定义了几个由微体系结构级别指定的级别。它们是累积的,因为来自先前级别的特性隐式地包含在后续级别中。

  • -march=x86-64: CMOV、CMPXCHG8B、FPU、FXSR、MMX、FXSR、SCE、SSE、SSE2

  • -march=x86-64-v2:(接近 Nehalem)CMPXCHG16B、LAHF-SAHF、POPCNT、SSE3、SSE4.1、SSE4.2、SSSE3

  • -march=x86-64-v3:(接近 Haswell)AVX、AVX2、BMI1、BMI2、F16C、FMA、LZCNT、MOVBE、XSAVE

  • -march=x86-64-v4: AVX512F、AVX512BW、AVX512CD、AVX512DQ、AVX512VL

英特尔 AVX10 ISA 是一个主要的全新向量 ISA,它融合了英特尔 AVX-512 的现代向量化方面。此 ISA 将在所有未来的英特尔处理器上得到支持。用户应该在这些处理器上使用新的选项 -mavx10.N-mavx10.N-512,并且不应再使用传统的 AVX512 选项。

N-mavx10.N 中表示一个从 1 开始的连续整数。 -mavx10.N-mavx10.N-256 的别名,这意味着在最大向量长度为 256 位的情况下启用 AVX10 版本 N 中的所有指令。 -mavx10.N-512 以最大向量长度为 512 位启用所有指令,它是 -mavx10.N 启用的指令的超集。

使用 AVX512 特性构建的当前二进制文件可以在支持英特尔 AVX10/512 的处理器上运行,无需重新编译,但不能在支持 AVX10/256 的处理器上运行。如果用户想要在支持 AVX10/256 的处理器上运行,则需要使用 -mavx10.N 重新编译代码,并可能更新某些调用 512 位 X86 特定内联函数并通过函数调用传递或返回 512 位向量类型的代码。使用 -mavx10.N 构建的二进制文件可以在支持 AVX10/256 和 AVX10/512 的处理器上运行。

如果用户想要在支持传统 AVX512 和新的 AVX10/256 的处理器上运行二进制文件,可以在命令行中添加 -mno-evex512,并使用 AVX512 选项。此选项具有与 -mavx10.N 相同的约束,即不能调用 512 位 X86 特定内联函数,也不能通过函数调用传递或返回 512 位向量类型。

在为 AVX10 开发代码时,用户应避免在函数目标属性中使用 AVX512 特性。如果必须这样做,则需要为 512 位或非 512 位函数分别添加显式 evex512no-evex512,以及 AVX512 特性,以避免意外的代码生成。EVEX512 特性的命令行选项和目标属性只能与 AVX512 一起使用。它们不会影响 AVX10 的向量大小。

用户不应在任何时候混合使用 AVX10 和 AVX512 选项,因为选项组合有时会发生冲突。例如, -mavx512f -mavx10.1-256 的组合不能清楚地表达对编译器的意图,因为 AVX512F 和 AVX10.1/256 中的指令相交但不重叠。在这种情况下,编译器将为此发出警告,但行为已确定。它将生成与选项 -mavx10.1-512 相同的代码。类似的情况是 -mavx512f -mavx10.2-256,它等同于 -mavx10.1-512 -mavx10.2-256,因为 avx10.2-256 意味着 avx10.1-256,而 -mavx512f -mavx10.1-256 等同于 -mavx10.1-512

支持 AVX10 时引入了一些新的宏。 -mavx10.1-256 将启用 __AVX10_1____EVEX256__,而 -mavx10.1-512 启用 __AVX10_1____EVEX256____EVEX512____AVX10_1_512__。此外, -mavx10.1-256-mavx10.1-512 都将启用所有 AVX512 特性特定的宏。AVX512 特性将启用 __EVEX256____EVEX512__ 及其自身宏。因此, __EVEX512__ 可用于保护可以在支持传统 AVX512 和 AVX10/512 的处理器上运行,但不能在支持 AVX10/256 的处理器上运行的代码,而 AVX512 宏(如 __AVX512F__)则无法区分这三种选项。如果用户想要进行区分,则需要检查附加宏 __AVX10_1____EVEX512__

ARM

对于 ARM(特别是 ARMv6 和 ARMv7)的支持在 Darwin(iOS)上被认为是稳定的:它已被测试可以正确编译许多大型 C、C++、Objective-C 和 Objective-C++ 代码库。clang 仅支持有限数量的 ARM 架构。例如,它尚不支持 ARMv5。

PowerPC

对于 PowerPC(尤其是 PowerPC64)的支持在 Linux 和 FreeBSD 上被认为是稳定的:它已被测试可以正确编译许多大型 C 和 C++ 代码库。PowerPC(32 位)仍然缺少某些特性(例如 ELF 平台上的 PIC 代码)。

其他平台

clang 目前包含对其他架构(例如 Sparc)的一些支持;但是,代码生成的重要部分仍然缺失,并且尚未经过重大测试。

clang 包含对 MSP430 嵌入式处理器的有限支持,但 clang 支持和 LLVM 后端支持均处于高度实验阶段。

目前,其他平台完全不受支持。在新的平台上添加解析和语义分析所需的最低限度支持非常容易;请参阅 clang 源代码树中的 lib/Basic/Targets.cpp。此级别的支持也足以将简单程序转换为 LLVM IR。对于 LLVM IR 的适当转换,目前需要在 lib/CodeGen/CGCall.cpp 中添加代码;不过,这可能会很快改变。生成汇编需要合适的 LLVM 后端。

操作系统特性和限制

Windows

clang 实验性地支持针对“Cygming”(Cygwin / MinGW)平台。

另请参阅 Microsoft 扩展

Cygwin

clang 在 Cygwin-1.7 上运行。

MinGW32

clang 在某些 mingw32 发行版上运行。clang 假设目录如下;

  • C:/mingw/include

  • C:/mingw/lib

  • C:/mingw/lib/gcc/mingw32/4.[3-5].0/include/c++

在 MSYS 上,一些测试可能会失败。

MinGW-w64

对于 32 位(i686-w64-mingw32)和 64 位(x86_64-w64-mingw32),clang 假设如下;

  • GCC 版本 4.5.0 4.5.34.6.0 4.6.2 4.7.0 (用于 C++ 头文件 搜索 路径)

  • some_directory/bin/gcc.exe

  • some_directory/bin/clang.exe

  • some_directory/bin/clang++.exe

  • some_directory/bin/../include/c++/GCC_version

  • some_directory/bin/../include/c++/GCC_version/x86_64-w64-mingw32

  • some_directory/bin/../include/c++/GCC_version/i686-w64-mingw32

  • some_directory/bin/../include/c++/GCC_version/backward

  • some_directory/bin/../x86_64-w64-mingw32/include

  • some_directory/bin/../i686-w64-mingw32/include

  • some_directory/bin/../include

此目录布局是您将在官方 MinGW-w64 网站 上找到的任何工具链的标准。

clang 预期 PATH 上存在为 i686-w64-mingw32(或 x86_64-w64-mingw32)编译的 GCC 可执行文件“gcc.exe”。

一些测试可能会失败x86_64-w64-mingw32 上。

AIX

TOC 数据转换

TOC 数据转换默认情况下是关闭的 (-mno-tocdata)。当指定 -mtocdata 时,TOC 数据转换将应用于所有具有静态存储期的合适变量,包括类的静态数据成员和块范围静态变量(如果未标记为异常,请参阅下文)。

合适的变量必须

  • 具有完整类型

  • 独立生成(即,不放在池中)

  • 最大不超过指针大小

  • 对齐不比指针更严格

  • 不是包含灵活数组成员的结构

  • 没有内部链接

  • 没有别名

  • 没有节属性

  • 不是线程局部存储

TOC 数据转换的结果是将变量(而不是它的地址)放置在 TOC 中。这消除了从 TOC 加载变量地址的需要。

注意:如果 TOC 数据转换应用于定义被导入的变量,链接器将生成用于读取或写入变量的修正代码。

当使用多个 toc-data 选项时,最后一个使用的选项具有影响。例如:-mno-tocdata=g5,g1 -mtocdata=g1,g2 -mno-tocdata=g2 -mtocdata=g3,g4 结果为 -mtocdata=g1,g3,g4

没有外部链接的变量名将被忽略。

选项

-mno-tocdata

这是默认行为。只有使用 -mtocdata= 显式指定的变量才会应用 TOC 数据转换。

-mtocdata

将 TOC 数据转换应用于所有具有静态存储期(包括类的静态数据成员和块范围静态变量)的合适变量,这些变量没有使用 -mno-tocdata= 显式指定。

-mno-tocdata=

可以与 -mtocdata 结合使用,以将使用其 mangled 名字指定的逗号分隔的外部链接变量列表标记为 -mtocdata 的例外。

-mtocdata=

如果合适,将 TOC 数据转换应用于使用其 mangled 名字指定的逗号分隔的外部链接变量列表。为所有指定的无法使用的变量发出诊断信息。

默认可见性导出映射

选项 -mdefault-visibility-export-mapping= 可用于控制将默认可见性映射到显式共享对象导出(即 XCOFF 导出可见性)。选项提供了三个值

  • -mdefault-visibility-export-mapping=none:不会为具有默认可见性的实体创建额外的导出信息。

  • -mdefault-visibility-export-mapping=explicit:如果实体从源代码(包括 RTTI)具有显式(例如通过属性)默认可见性,则将其标记为导出。

  • -mdefault-visibility-export-mapping=all:为所有从任何来源具有默认可见性的实体设置 XCOFF 导出可见性。这将提供与 ELF 平台类似的导出行为,其中所有具有默认可见性的实体都将导出。

SPIR-V 支持

Clang 支持生成符合 OpenCL 环境规范 的 SPIR-V。

为了生成 SPIR-V 二进制文件,Clang 使用来自 SPIRV-LLVM-Translator 仓库 的外部工具 llvm-spirv

在使用 Clang 生成 SPIR-V 二进制文件之前,应构建或安装 llvm-spirv。有关详细信息,请参阅 以下说明。Clang 将在 PATH 环境变量中依次查找 llvm-spirv-<LLVM-major-version>llvm-spirv 可执行文件。Clang 使用 llvm-spirv 以及 广泛采用的汇编语法包

版本控制 llvm-spirv 与 Clang 主要版本保持一致。主开发分支也是如此。因此,确保 llvm-spirv 版本与 Clang 版本一致非常重要。为了进行故障排除,可以 独立测试 llvm-spirv

OpenCL 内核编译的示例用法

$ clang --target=spirv32 -c test.cl
$ clang --target=spirv64 -c test.cl

Clang 的两次调用都将生成一个分别用于 32 位和 64 位的 SPIR-V 二进制文件 test.o。该文件可以被支持 SPIR-V 消费的 OpenCL 驱动程序导入,或者可以被离线 SPIR-V 消费工具进一步编译。

目前,使用除 -O0 以外的优化级别生成的 SPIR-V 转换是一个实验性功能,不保证在所有情况下都能正常工作。

当命令行中传递 -fintegrated-objemitter 标志时,Clang 还支持作为实验性功能集成生成 SPIR-V,而无需使用 llvm-spirv 工具。

$ clang --target=spirv32 -fintegrated-objemitter -c test.cl

请注意,目前只支持非常基本的功能,因此它不适用于任意用例。仅当 clang 构建配置为使用 -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=SPIRV 选项时,此功能才启用。

链接使用来自 SPIRV-Tools 项目spirv-link。与其他外部链接器类似,Clang 期望 spirv-link 被单独安装并在 PATH 环境变量中存在。请参阅 构建和安装说明

$ clang --target=spirv64 test1.cl test2.cl

有关 SPIR-V 目标设置和支持的 SPIR-V 格式版本的更多信息,请参阅 SPIR-V 目标指南

clang-cl

clang-cl 是 Clang 的一个替代命令行界面,旨在与 Visual C++ 编译器 cl.exe 兼容。

为了使 clang-cl 能够在从命令行运行时找到系统头文件、库和链接器,它应该在 Visual Studio 本机工具命令提示符或已使用例如 vcvarsall.bat 设置环境的常规命令提示符中执行。

clang-cl 也可以从 Visual Studio 内部使用,方法是选择 LLVM 平台工具集。工具集不是安装程序的一部分,但可以从 Visual Studio 市场 单独安装。要使用工具集,请在解决方案资源管理器中选择一个项目,打开它的属性页(Alt+F7),并在“配置属性”的“常规”部分将“平台工具集”更改为 LLVM。这样做将启用一个额外的属性页,用于选择用于构建的 clang-cl 可执行文件。

要直接使用工具集与 MSBuild,请使用例如 /p:PlatformToolset=LLVM 调用它。这允许在不修改项目文件的情况下试用 clang-cl 工具链。

也可以在不更改工具集的情况下,通过传递 /p:CLToolPath=c:\llvm\bin /p:CLToolExe=clang-cl.exe 将 MSBuild 指向 clang-cl。

当使用 CMake 和 Visual Studio 生成器时,可以使用 -T 标志设置工具集

cmake -G"Visual Studio 16 2019" -T LLVM ..

当使用 CMake 与 Ninja 生成器时,将 CMAKE_C_COMPILERCMAKE_CXX_COMPILER 变量设置为 clang-cl

cmake -GNinja -DCMAKE_C_COMPILER="c:/Program Files (x86)/LLVM/bin/clang-cl.exe"
    -DCMAKE_CXX_COMPILER="c:/Program Files (x86)/LLVM/bin/clang-cl.exe" ..

命令行选项

为了与 cl.exe 兼容,clang-cl 支持大多数相同的命令行选项。这些选项可以以 /- 开头。它还支持一些 Clang 的核心选项,例如 -W 选项。

已知 clang-cl 但目前不支持的选项将被忽略,并显示警告。例如

clang-cl.exe: warning: argument unused during compilation: '/AI'

要抑制有关未使用参数的警告,请使用 -Qunused-arguments 选项。

默认情况下,clang-cl 不认识的选项将被忽略。使用 -Werror=unknown-argument 选项将它们视为错误。如果这些选项以 / 开头,它们将被误认为是文件名

clang-cl.exe: error: no such file or directory: '/foobar'

提交错误报告,报告 clang-cl 不理解的任何有效的 cl.exe 标志。

执行 clang-cl /? 以查看支持的选项列表

CL.EXE COMPATIBILITY OPTIONS:
  /?                      Display available options
  /arch:<value>           Set architecture for code generation
  /Brepro-                Emit an object file which cannot be reproduced over time
  /Brepro                 Emit an object file which can be reproduced over time
  /clang:<arg>            Pass <arg> to the clang driver
  /C                      Don't discard comments when preprocessing
  /c                      Compile only
  /d1PP                   Retain macro definitions in /E mode
  /d1reportAllClassLayout Dump record layout information
  /diagnostics:caret      Enable caret and column diagnostics (on by default)
  /diagnostics:classic    Disable column and caret diagnostics
  /diagnostics:column     Disable caret diagnostics but keep column info
  /D <macro[=value]>      Define macro
  /EH<value>              Exception handling model
  /EP                     Disable linemarker output and preprocess to stdout
  /execution-charset:<value>
                          Runtime encoding, supports only UTF-8
  /E                      Preprocess to stdout
  /FA                     Output assembly code file during compilation
  /Fa<file or directory>  Output assembly code to this file during compilation (with /FA)
  /Fe<file or directory>  Set output executable file or directory (ends in / or \)
  /FI <value>             Include file before parsing
  /Fi<file>               Set preprocess output file name (with /P)
  /Fo<file or directory>  Set output object file, or directory (ends in / or \) (with /c)
  /fp:except-
  /fp:except
  /fp:fast
  /fp:precise
  /fp:strict
  /Fp<filename>           Set pch filename (with /Yc and /Yu)
  /GA                     Assume thread-local variables are defined in the executable
  /Gd                     Set __cdecl as a default calling convention
  /GF-                    Disable string pooling
  /GF                     Enable string pooling (default)
  /GR-                    Disable emission of RTTI data
  /Gregcall               Set __regcall as a default calling convention
  /GR                     Enable emission of RTTI data
  /Gr                     Set __fastcall as a default calling convention
  /GS-                    Disable buffer security check
  /GS                     Enable buffer security check (default)
  /Gs                     Use stack probes (default)
  /Gs<value>              Set stack probe size (default 4096)
  /guard:<value>          Enable Control Flow Guard with /guard:cf,
                          or only the table with /guard:cf,nochecks.
                          Enable EH Continuation Guard with /guard:ehcont
  /Gv                     Set __vectorcall as a default calling convention
  /Gw-                    Don't put each data item in its own section
  /Gw                     Put each data item in its own section
  /GX-                    Disable exception handling
  /GX                     Enable exception handling
  /Gy-                    Don't put each function in its own section (default)
  /Gy                     Put each function in its own section
  /Gz                     Set __stdcall as a default calling convention
  /help                   Display available options
  /imsvc <dir>            Add directory to system include search path, as if part of %INCLUDE%
  /I <dir>                Add directory to include search path
  /J                      Make char type unsigned
  /LDd                    Create debug DLL
  /LD                     Create DLL
  /link <options>         Forward options to the linker
  /MDd                    Use DLL debug run-time
  /MD                     Use DLL run-time
  /MTd                    Use static debug run-time
  /MT                     Use static run-time
  /O0                     Disable optimization
  /O1                     Optimize for size  (same as /Og     /Os /Oy /Ob2 /GF /Gy)
  /O2                     Optimize for speed (same as /Og /Oi /Ot /Oy /Ob2 /GF /Gy)
  /Ob0                    Disable function inlining
  /Ob1                    Only inline functions which are (explicitly or implicitly) marked inline
  /Ob2                    Inline functions as deemed beneficial by the compiler
  /Ob3                    Same as /Ob2
  /Od                     Disable optimization
  /Og                     No effect
  /Oi-                    Disable use of builtin functions
  /Oi                     Enable use of builtin functions
  /Os                     Optimize for size (like clang -Os)
  /Ot                     Optimize for speed (like clang -O3)
  /Ox                     Deprecated (same as /Og /Oi /Ot /Oy /Ob2); use /O2 instead
  /Oy-                    Disable frame pointer omission (x86 only, default)
  /Oy                     Enable frame pointer omission (x86 only)
  /O<flags>               Set multiple /O flags at once; e.g. '/O2y-' for '/O2 /Oy-'
  /o <file or directory>  Set output file or directory (ends in / or \)
  /P                      Preprocess to file
  /Qvec-                  Disable the loop vectorization passes
  /Qvec                   Enable the loop vectorization passes
  /showFilenames-         Don't print the name of each compiled file (default)
  /showFilenames          Print the name of each compiled file
  /showIncludes           Print info about included files to stderr
  /source-charset:<value> Source encoding, supports only UTF-8
  /std:<value>            Language standard to compile for
  /TC                     Treat all source files as C
  /Tc <filename>          Specify a C source file
  /TP                     Treat all source files as C++
  /Tp <filename>          Specify a C++ source file
  /utf-8                  Set source and runtime encoding to UTF-8 (default)
  /U <macro>              Undefine macro
  /vd<value>              Control vtordisp placement
  /vmb                    Use a best-case representation method for member pointers
  /vmg                    Use a most-general representation for member pointers
  /vmm                    Set the default most-general representation to multiple inheritance
  /vms                    Set the default most-general representation to single inheritance
  /vmv                    Set the default most-general representation to virtual inheritance
  /volatile:iso           Volatile loads and stores have standard semantics
  /volatile:ms            Volatile loads and stores have acquire and release semantics
  /W0                     Disable all warnings
  /W1                     Enable -Wall
  /W2                     Enable -Wall
  /W3                     Enable -Wall
  /W4                     Enable -Wall and -Wextra
  /Wall                   Enable -Weverything
  /WX-                    Do not treat warnings as errors
  /WX                     Treat warnings as errors
  /w                      Disable all warnings
  /X                      Don't add %INCLUDE% to the include search path
  /Y-                     Disable precompiled headers, overrides /Yc and /Yu
  /Yc<filename>           Generate a pch file for all code up to and including <filename>
  /Yu<filename>           Load a pch file and use it instead of all code up to and including <filename>
  /Z7                     Enable CodeView debug information in object files
  /Zc:char8_t             Enable C++20 char8_t type
  /Zc:char8_t-            Disable C++20 char8_t type
  /Zc:dllexportInlines-   Don't dllexport/dllimport inline member functions of dllexport/import classes
  /Zc:dllexportInlines    dllexport/dllimport inline member functions of dllexport/import classes (default)
  /Zc:sizedDealloc-       Disable C++14 sized global deallocation functions
  /Zc:sizedDealloc        Enable C++14 sized global deallocation functions
  /Zc:strictStrings       Treat string literals as const
  /Zc:threadSafeInit-     Disable thread-safe initialization of static variables
  /Zc:threadSafeInit      Enable thread-safe initialization of static variables
  /Zc:trigraphs-          Disable trigraphs (default)
  /Zc:trigraphs           Enable trigraphs
  /Zc:twoPhase-           Disable two-phase name lookup in templates
  /Zc:twoPhase            Enable two-phase name lookup in templates
  /Zi                     Alias for /Z7. Does not produce PDBs.
  /Zl                     Don't mention any default libraries in the object file
  /Zp                     Set the default maximum struct packing alignment to 1
  /Zp<value>              Specify the default maximum struct packing alignment
  /Zs                     Run the preprocessor, parser and semantic analysis stages

OPTIONS:
  -###                    Print (but do not run) the commands to run for this compilation
  --analyze               Run the static analyzer
  -faddrsig               Emit an address-significance table
  -fansi-escape-codes     Use ANSI escape codes for diagnostics
  -fblocks                Enable the 'blocks' language feature
  -fcf-protection=<value> Instrument control-flow architecture protection. Options: return, branch, full, none.
  -fcf-protection         Enable cf-protection in 'full' mode
  -fcolor-diagnostics     Use colors in diagnostics
  -fcomplete-member-pointers
                          Require member pointer base types to be complete if they would be significant under the Microsoft ABI
  -fcoverage-mapping      Generate coverage mapping to enable code coverage analysis
  -fcrash-diagnostics-dir=<dir>
                          Put crash-report files in <dir>
  -fdebug-macro           Emit macro debug information
  -fdelayed-template-parsing
                          Parse templated function definitions at the end of the translation unit
  -fdiagnostics-absolute-paths
                          Print absolute paths in diagnostics
  -fdiagnostics-parseable-fixits
                          Print fix-its in machine parseable form
  -flto=<value>           Set LTO mode to either 'full' or 'thin'
  -flto                   Enable LTO in 'full' mode
  -fmerge-all-constants   Allow merging of constants
  -fmodule-file=<module_name>=<module-file>
                          Use the specified module file that provides the module <module_name>
  -fmodule-header=<header>
                          Build <header> as a C++20 header unit
  -fmodule-output=<path>
                          Save intermediate module file results when compiling a standard C++ module unit.
  -fms-compatibility-version=<value>
                          Dot-separated value representing the Microsoft compiler version
                          number to report in _MSC_VER (0 = don't define it; default is same value as installed cl.exe, or 1933)
  -fms-compatibility      Enable full Microsoft Visual C++ compatibility
  -fms-extensions         Accept some non-standard constructs supported by the Microsoft compiler
  -fmsc-version=<value>   Microsoft compiler version number to report in _MSC_VER
                          (0 = don't define it; default is same value as installed cl.exe, or 1933)
  -fno-addrsig            Don't emit an address-significance table
  -fno-builtin-<value>    Disable implicit builtin knowledge of a specific function
  -fno-builtin            Disable implicit builtin knowledge of functions
  -fno-complete-member-pointers
                          Do not require member pointer base types to be complete if they would be significant under the Microsoft ABI
  -fno-coverage-mapping   Disable code coverage analysis
  -fno-crash-diagnostics  Disable auto-generation of preprocessed source files and a script for reproduction during a clang crash
  -fno-debug-macro        Do not emit macro debug information
  -fno-delayed-template-parsing
                          Disable delayed template parsing
  -fno-sanitize-address-poison-custom-array-cookie
                          Disable poisoning array cookies when using custom operator new[] in AddressSanitizer
  -fno-sanitize-address-use-after-scope
                          Disable use-after-scope detection in AddressSanitizer
  -fno-sanitize-address-use-odr-indicator
                           Disable ODR indicator globals
  -fno-sanitize-ignorelist Don't use ignorelist file for sanitizers
  -fno-sanitize-cfi-cross-dso
                          Disable control flow integrity (CFI) checks for cross-DSO calls.
  -fno-sanitize-coverage=<value>
                          Disable specified features of coverage instrumentation for Sanitizers
  -fno-sanitize-memory-track-origins
                          Disable origins tracking in MemorySanitizer
  -fno-sanitize-memory-use-after-dtor
                          Disable use-after-destroy detection in MemorySanitizer
  -fno-sanitize-recover=<value>
                          Disable recovery for specified sanitizers
  -fno-sanitize-stats     Disable sanitizer statistics gathering.
  -fno-sanitize-thread-atomics
                          Disable atomic operations instrumentation in ThreadSanitizer
  -fno-sanitize-thread-func-entry-exit
                          Disable function entry/exit instrumentation in ThreadSanitizer
  -fno-sanitize-thread-memory-access
                          Disable memory access instrumentation in ThreadSanitizer
  -fno-sanitize-trap=<value>
                          Disable trapping for specified sanitizers
  -fno-standalone-debug   Limit debug information produced to reduce size of debug binary
  -fno-strict-aliasing    Disable optimizations based on strict aliasing rules (default)
  -fobjc-runtime=<value>  Specify the target Objective-C runtime kind and version
  -fprofile-exclude-files=<value>
                          Instrument only functions from files where names don't match all the regexes separated by a semi-colon
  -fprofile-filter-files=<value>
                          Instrument only functions from files where names match any regex separated by a semi-colon
  -fprofile-generate=<dirname>
                          Generate instrumented code to collect execution counts into a raw profile file in the directory specified by the argument. The filename uses default_%m.profraw pattern
                          (overridden by LLVM_PROFILE_FILE env var)
  -fprofile-generate
                          Generate instrumented code to collect execution counts into default_%m.profraw file
                          (overridden by '=' form of option or LLVM_PROFILE_FILE env var)
  -fprofile-instr-generate=<file_name_pattern>
                          Generate instrumented code to collect execution counts into the file whose name pattern is specified as the argument
                          (overridden by LLVM_PROFILE_FILE env var)
  -fprofile-instr-generate
                          Generate instrumented code to collect execution counts into default.profraw file
                          (overridden by '=' form of option or LLVM_PROFILE_FILE env var)
  -fprofile-instr-use=<value>
                          Use instrumentation data for coverage testing or profile-guided optimization
  -fprofile-use=<value>
                          Use instrumentation data for profile-guided optimization
  -fprofile-remapping-file=<file>
                          Use the remappings described in <file> to match the profile data against names in the program
  -fprofile-list=<file>
                          Filename defining the list of functions/files to instrument
  -fsanitize-address-field-padding=<value>
                          Level of field padding for AddressSanitizer
  -fsanitize-address-globals-dead-stripping
                          Enable linker dead stripping of globals in AddressSanitizer
  -fsanitize-address-poison-custom-array-cookie
                          Enable poisoning array cookies when using custom operator new[] in AddressSanitizer
  -fsanitize-address-use-after-return=<mode>
                          Select the mode of detecting stack use-after-return in AddressSanitizer: never | runtime (default) | always
  -fsanitize-address-use-after-scope
                          Enable use-after-scope detection in AddressSanitizer
  -fsanitize-address-use-odr-indicator
                          Enable ODR indicator globals to avoid false ODR violation reports in partially sanitized programs at the cost of an increase in binary size
  -fsanitize-ignorelist=<value>
                          Path to ignorelist file for sanitizers
  -fsanitize-cfi-cross-dso
                          Enable control flow integrity (CFI) checks for cross-DSO calls.
  -fsanitize-cfi-icall-generalize-pointers
                          Generalize pointers in CFI indirect call type signature checks
  -fsanitize-coverage=<value>
                          Specify the type of coverage instrumentation for Sanitizers
  -fsanitize-hwaddress-abi=<value>
                          Select the HWAddressSanitizer ABI to target (interceptor or platform, default interceptor)
  -fsanitize-memory-track-origins=<value>
                          Enable origins tracking in MemorySanitizer
  -fsanitize-memory-track-origins
                          Enable origins tracking in MemorySanitizer
  -fsanitize-memory-use-after-dtor
                          Enable use-after-destroy detection in MemorySanitizer
  -fsanitize-recover=<value>
                          Enable recovery for specified sanitizers
  -fsanitize-stats        Enable sanitizer statistics gathering.
  -fsanitize-thread-atomics
                          Enable atomic operations instrumentation in ThreadSanitizer (default)
  -fsanitize-thread-func-entry-exit
                          Enable function entry/exit instrumentation in ThreadSanitizer (default)
  -fsanitize-thread-memory-access
                          Enable memory access instrumentation in ThreadSanitizer (default)
  -fsanitize-trap=<value> Enable trapping for specified sanitizers
  -fsanitize-undefined-strip-path-components=<number>
                          Strip (or keep only, if negative) a given number of path components when emitting check metadata.
  -fsanitize=<check>      Turn on runtime checks for various forms of undefined or suspicious
                          behavior. See user manual for available checks
  -fsplit-lto-unit        Enables splitting of the LTO unit.
  -fstandalone-debug      Emit full debug info for all types used by the program
  -fstrict-aliasing       Enable optimizations based on strict aliasing rules
  -fsyntax-only           Run the preprocessor, parser and semantic analysis stages
  -fwhole-program-vtables Enables whole-program vtable optimization. Requires -flto
  -gcodeview-ghash        Emit type record hashes in a .debug$H section
  -gcodeview              Generate CodeView debug information
  -gline-directives-only  Emit debug line info directives only
  -gline-tables-only      Emit debug line number tables only
  -miamcu                 Use Intel MCU ABI
  -mllvm <value>          Additional arguments to forward to LLVM's option processing
  -nobuiltininc           Disable builtin #include directories
  -Qunused-arguments      Don't emit warning for unused driver arguments
  -R<remark>              Enable the specified remark
  --target=<value>        Generate code for the given target
  --version               Print version information
  -v                      Show commands to run and use verbose output
  -W<warning>             Enable the specified warning
  -Xclang <arg>           Pass <arg> to the clang compiler

/clang: 选项

当 clang-cl 使用一组 /clang:<arg> 选项运行时,它将收集所有 <arg> 参数并像它们传递给 clang 驱动程序一样处理它们。这种机制允许您传递 clang-cl 选项中没有公开的标志或传递给 clang 驱动程序时含义不同的标志。无论它们在命令行中的位置如何,/clang: 参数都将被视为在 clang-cl 命令行的末尾传递。

/Zc:dllexportInlines- 选项

这将导致类级别的 dllexportdllimport 属性不应用于内联成员函数,因为它们通常会应用。例如,在下面的代码中,S::foo() 通常将由 DLL 定义和导出,但在使用 /Zc:dllexportInlines- 标志时,它不会被导出

struct __declspec(dllexport) S {
  void foo() {}
}

这有以下好处:编译器不需要在包含声明的每个翻译单元中生成 S::foo() 的定义,否则它会这样做,以确保 DLL 中存在定义,即使它在那里没有使用。如果声明出现在广泛使用的头文件中,这可以节省大量的编译时间和输出大小。它还减少了 DLL 导出的函数数量,类似于 -fvisibility-inlines-hidden 对 ELF 和 Mach-O 上的共享对象所做的操作。由于函数声明带有内联定义,库用户可以直接使用该定义,而不是从 DLL 中导入它。

请注意,Microsoft Visual C++ 编译器不支持此选项,如果 DLL 中的代码使用 /Zc:dllexportInlines- 编译,则使用 DLL 的代码必须以相同的方式编译,以确保它不会尝试 dllimport 内联成员函数。但是,反向场景通常应该可以工作:使用此标志编译的 DLL(例如,使用 Visual C++ 编译的系统库)可以从使用此标志编译的代码中引用,这意味着引用代码将使用内联定义而不是从 DLL 中导入它们。

还要注意,就像使用 -fvisibility-inlines-hidden 一样,S::foo() 的地址在 DLL 内部和外部将不同,这违反了 C/C++ 标准要求函数具有唯一地址的要求。

此标志不适用于显式类模板实例化定义或声明,因为这些定义通常用于在 DLL 中显式提供单个定义(dllexported 实例化定义),或者用于指示定义在其他地方可用(dllimport 实例化声明)。它也不适用于具有静态局部变量的内联成员,以确保在 DLL 内部和外部使用相同实例的变量。

使用此标志可能会导致问题,当内联函数(否则将被 dllexported)引用 DLL 的内部符号时。例如

void internal();

struct __declspec(dllimport) S {
  void foo() { internal(); }
}

通常,对 S::foo() 的引用将使用它从其导出的 DLL 中的定义,该 DLL 也可能包含 internal() 的定义。但是,当使用 /Zc:dllexportInlines- 时,将直接使用 S::foo() 的内联定义,导致链接错误,因为 internal() 不可用。更糟糕的是,如果存在 internal() 的内联定义,其中包含一个静态局部变量,我们现在将引用与 DLL 中不同的实例的该变量

inline int internal() { static int x; return x++; }

struct __declspec(dllimport) S {
  int foo() { return internal(); }
}

这可能会导致非常微妙的错误。使用 -fvisibility-inlines-hidden 可能会导致相同的问题。为了避免这种情况,请将 S::foo()internal() 设置为非内联,或显式地标记它们为 dllimport/dllexport

查找 Clang 运行时库

clang-cl 支持几个需要运行时库支持的功能

  • 地址清理器 (ASan):-fsanitize=address

  • 未定义行为清理器 (UBSan):-fsanitize=undefined

  • 代码覆盖率:-fprofile-instr-generate -fcoverage-mapping

  • 配置文件引导优化 (PGO):-fprofile-generate

  • 某些数学运算(int128 除法)需要内置库

为了使用这些功能,用户必须将正确的运行时库链接到他们的程序中。这些库与 Clang 一起分发在库资源目录中。Clang 通过搜索相对于 Clang 可执行文件的位置来搜索资源目录。例如,如果 LLVM 安装在 C:\Program Files\LLVM 中,则配置文件运行时库将位于路径 C:\Program Files\LLVM\lib\clang\11.0.0\lib\windows\clang_rt.profile-x86_64.lib

对于 UBSan、PGO 和覆盖率,Clang 将生成自动链接相应运行时库的对象文件,但用户通常需要帮助链接器(无论是 lld-link.exe 还是 MSVC link.exe)找到库资源目录。使用上面的示例安装,这意味着将 /LIBPATH:C:\Program Files\LLVM\lib\clang\11.0.0\lib\windows 传递给链接器。如果用户使用 clangclang-cl 驱动程序链接程序,驱动程序将为他们传递此标志。

可以使用 -fno-rtlib-defaultlib 禁用自动链接。如果使用该标志,请像下面针对 ASan 描述的那样,将完整的标志传递给所需库。

如果链接器找不到合适的库,它将发出类似以下错误

$ clang-cl -c -fsanitize=undefined t.cpp

$ lld-link t.obj -dll
lld-link: error: could not open 'clang_rt.ubsan_standalone-x86_64.lib': no such file or directory
lld-link: error: could not open 'clang_rt.ubsan_standalone_cxx-x86_64.lib': no such file or directory

$ link t.obj -dll -nologo
LINK : fatal error LNK1104: cannot open file 'clang_rt.ubsan_standalone-x86_64.lib'

要修复错误,请在链接行中添加适当的 /libpath: 标志。

对于 ASan,截至撰写本文时,用户还负责链接到正确的 ASan 库。

如果用户使用动态 CRT (/MD),则他们应该在链接行中添加 clang_rt.asan_dynamic-x86_64.lib 作为常规输入。对于其他体系结构,请将 x86_64 替换为此处和下面相应的名称。

如果用户使用静态 CRT (/MT),则使用不同的运行时来生成 DLL 和 EXE。要链接 DLL,请传递 clang_rt.asan_dll_thunk-x86_64.lib。要链接 EXE,请传递 -wholearchive:clang_rt.asan-x86_64.lib

Windows 系统头文件和库查找

clang-cl 使用一组不同的方法来定位在构建代码时要链接的正确系统库。Windows 环境使用来自三个不同来源的库

  1. Windows SDK

  2. UCRT(通用 C 运行时)

  3. Visual C++ 工具(VCRuntime)

Windows SDK 提供了针对 Windows 系统包构建程序所需的导入库和头文件。Windows SDK 的基础是 UCRT,即通用 C 运行时。

这种区别可以通过在不同类别中找到的各种头文件来最好地说明。WinSDK 将包含诸如 WinSock2.h 之类的头文件,它是 Windows API 表面的一部分,提供了用于网络的 Windows 套接字接口。UCRT 提供 C 库头文件,包括例如 stdio.h。最后,Visual C++ 工具提供了基础的 Visual C++ 运行时头文件,例如 stdint.hcrtdefs.h

有一些控制允许用户控制 clang-cl 将在哪里定位这些头文件。Windows SDK 和 UCRT 的默认行为如下

  1. 查询命令行。

    用户指定的任何内容都始终优先。以下扩展是 clang-cl 工具集的一部分

    • /winsysroot

    /winsysroot: 用作 Unix 环境中 -sysroot 的等效项。它允许控制将备用位置视为系统根。指定时,它将用作 Windows Kits 所在的根目录。

    • /winsdkversion

    • /winsdkdir

    如果未指定 /winsysroot:,则将查询 /winsdkdir: 参数作为位置来识别 Windows SDK 所在的位置。与 /winsysroot: 相反,/winsdkdir: 预计是完整路径,而不是找到 Windows Kits 的根目录。

    /winsdkversion: 标志允许用户指定要优先使用的 SDK 的版本标识符。指定此版本后,不会执行其他验证,并将优先使用此版本。如果未指定版本,将使用检测到的最高版本号。

  2. 查询环境。

    TODO:尚未实现。

    这将查询环境变量

    • WindowsSdkDir

    • UCRTVersion

  3. 回退到注册表。

    如果没有使用任何参数来指示 SDK 所在的位置,并且编译器在 Windows 上运行,则将查询注册表以找到安装位置。

Visual C++ 工具集具有稍微更复杂的检测机制。

  1. 查询命令行。

    • /winsysroot

    /winsysroot: 用作 Unix 环境中 -sysroot 的等效项。它允许控制将备用位置视为系统根。指定时,它将用作 VC 目录所在的根目录。

    • /vctoolsdir

    • /vctoolsversion

    如果未指定 /winsysroot:,则将查询 /vctoolsdir: 参数作为位置来识别 Visual C++ 工具所在的位置。如果指定了 /vctoolsversion:,则优先使用该版本,否则,将使用检测到的最高版本。

  2. 查询环境。

    • /external:[VARIABLE]

      这指定了一个用户识别的环境变量,该变量被视为路径分隔符 (;) 分隔的路径列表,以映射到 -imsvc 参数中,这些参数被视为 -isystem

    • INCLUDEEXTERNAL_INCLUDE

      路径分隔符 (;) 分隔的路径列表将被映射到 -imsvc 参数中,这些参数被视为 -isystem

    • LIB(间接)

      链接器 link.exelld-link.exe 将遵循环境变量 LIB,该变量是路径分隔符 (;) 设置的路径集,用于在链接最终目标时查询要使用的导入库。

    将查询以下环境变量,并使用它们来形成路径,以便根据需要验证和加载内容

    • VCToolsInstallDir

    • VCINSTALLDIR

    • Path

  3. 查询 ISetupConfiguration [仅限 Windows]

    假设工具链是在定义了 USE_MSVC_SETUP_API 的情况下构建的,并且在 Windows 上运行,则将使用 Visual Studio COM 接口 ISetupConfiguration 来找到 MSVC 工具集的安装位置。

  4. 回退到注册表 [已弃用]

    注册表信息用于帮助找到安装位置作为最后的回退。这仅适用于 VS2017 之前的安装,并且被认为已弃用。

与 Clang 相比的限制和局限性

严格别名

严格别名 (TBAA) 在 clang-cl 中始终默认关闭。而在 clang 中,严格别名默认情况下在所有优化级别都打开。

要启用基于严格别名规则的 LLVM 优化(例如,基于 C/C++ 中表达式类型的优化),用户需要显式地将 -fstrict-aliasing 传递给 clang-cl。