本文档说明如何使用 -xalias_level 选项和几个 pragma,以便编译器可以执行基于类型的别名分析和优化。您可以使用这些扩展功能表示关于 C 程序中使用指针方法的基于类型的信息。C 编译器又可以使用此信息对程序中基于指针的内存引用更好地进行别名歧义消除并且效果显著。
有关此命令语法的详细说明,请参见B.2.72 -xalias_level[= l]。此外,有关 lint 程序的基于类型的别名分析功能的说明,请参见4.3.38 -Xalias_level[=l ]。
可以使用 -xalias_level 选项指定七个别名级别之一。每个级别指定一组关于您在 C 程序中使用指针的方法的属性。
当您使用 -xalias_level 选项的较高级别进行编译时,编译器会对您的代码中的指针进行更广泛的假定。当编译器产生较少假定时,您有更大的编程自由。但是,这些狭窄假定产生的优化可能不会导致运行环境性能的显著提高。如果您依照 -xalias_level 选项的更高级别的编译器假定进行编码,则更有可能使产生的优化提高运行环境性能。
-xalias_level 选项指定应用于每个转换单元的别名级别。在越详细越有益的情况下,您可以使用新的 pragma 覆盖已生效的别名级别,以便可以明确指定转换单元中个体类型或指针变量之间的别名关系。如果转换单元中指针的使用对应于某个可用别名级别,但是一些特定指针变量的使用方法是某个可用级别不允许的不规则方法,这些 pragma 非常有用。
如果进行基于类型的分析时,更详细的信息可为您提供帮助,则可以使用以下 pragma 覆盖已生效的别名级别,并指定转换单元中个体类型或指针变量之间的别名关系。如果转换单元中指针的使用与某个可用别名级别一致,但是一些特定指针变量的使用方法是某个可用级别不允许的不规则方法,这些 pragma 非常有益。
必须在 pragma 之前声明命名的类型或变量,否则会发出警告消息并忽略 pragma。如果 pragma 出现在其含义所适用的第一个内存引用之后,则程序的结果不确定。
下列术语用于 pragma 定义。
术语 |
含义 |
---|---|
level |
B.2.72 -xalias_level[= l]下列出的任何别名级别。 |
type |
以下任何类型:
|
pointer_name |
转换单元中指针类型的任何变量的名称。 |
将 level 替换为以下七个别名级别之一:any、basic、weak、layout、strict、std 或 strong。您可以使用单一类型或逗号分隔的类型列表替换 list,也可以使用单一指针或逗号分隔的指针列表替换 list。例如,您可以按以下方式发出 #pragma alias_level:
#pragma alias_level level (type [, type] )
#pragma alias_level level (pointer [, pointer] )
此 pragma 指定,指示的别名级别应用于所列类型的转换单元的所有内存引用,或者应用于其中某个命名指针变量正在被非关联化的转换单元的所有非关联化。
如果您指定多个要应用于特定非关联化的别名级别,则指针名称(如果有)应用的级别优先于所有其他级别。类型名称(如果有)应用的级别优先于选项应用的级别。在以下示例中,如果在编译程序时将 #pragma alias_level 设置得比 any 高,则 std 级别应用于 p。
typedef int * int_ptr; int_ptr p; #pragma alias_level strong (int_ptr) #pragma alias_level std (p) |
此 pragma 指定所有列出的类型互为别名。在以下示例中,编译器假定间接访问 *pt 的别名为间接访问 *pf。
#pragma alias (int, float) int *pt; float *pf; |
此 pragma 指定,在任何命名指针变量的任何非关联化点,正被非关联化的指针值可以指向与任何其他命名指针变量相同的对象。但是,指针并不仅限于命名变量中包含的对象,可以指向列表中未包含的对象。此 pragma 覆盖应用的任何别名级别的别名假定。在以下示例中,该 pragma 之后对 p 和 q 的任何间接访问无论是什么类型,均被视为别名。
#pragma alias(p, q) |
此 pragma 指定,在进行命名指针变量的任何非关联化操作时,被非关联化的指针值可以指向任何命名变量中包含的对象。但是,指针并不仅限于命名变量中包含的对象,可以指向列表中未包含的对象。此 pragma 覆盖应用的任何别名级别的别名假定。在以下示例中,编译器假定对 *p 的任何间接访问的别名可以是任何直接访问 a、b 和 c。
#pragma alias may_point_to(p, a, b, c) |
此 pragma 指定列出的类型不互为别名。在以下示例中,编译器假定对 *p 的任何间接访问不将间接访问 *ps 作为别名。
struct S { float f; ...} *ps; #pragma noalias(int, struct S) int *p; |
此 pragma 指定,在进行任何命名指针变量的任何非关联化操作时,被非关联化的指针值不指向与任何其他命名指针变量相同的对象。此 pragma 覆盖所有其他应用的别名级别。在以下示例中,编译器假定无论两个指针是什么类型,对 *p 的任何间接访问均不将间接访问 *q 作为别名。
#pragma noalias(p, q) |
此 pragma 指定,在命名指针变量的任何非关联化点,正被非关联化的指针值不指向任何命名变量中包含的对象。此 pragma 覆盖所有其他应用的别名级别。在以下示例中,编译器假定 *p 的任何间接访问与 a、b 或 c 的直接访问不为别名。
#pragma may_not_point_to(p, a, b, c) |
lint 程序识别与编译器的 -xalias_level 命令同级别的基于类型的别名歧义消除。lint 程序还识别与本章中说明的基于类型的别名歧义消除相关的 pragma。有关 lint -Xalias_level 命令的详细说明,请参见4.3.38 -Xalias_level[=l ]。
lint 检测以下四种情况并生成警告:
将标量指针强制转换为结构指针
将空指针强制转换为结构指针
将结构字段强制转换为标量指针
将结构指针强制转换为 -Xalias_level=strict 级别上没有显式别名的结构指针。
在以下示例中,整型指针 p 强制转换为 struct foo 类型的指针。如果 lint -Xalias_level=weak(或更高),这将生成错误。
struct foo { int a; int b; }; struct foo *f; int *p; void main() { f = (struct foo *)p; /* struct pointer cast of scalar pointer error */ } |
在以下示例中,空指针 vp 强制转换为结构指针。如果 lint -Xalias_level=weak(或更高),这将生成警告。
struct foo { int a; int b; }; struct foo *f; void *vp; void main() { f = (struct foo *)vp; /* struct pointer cast of void pointer warning */ } |
在下面的示例中,结构成员 foo.b 的地址被强制转换为结构指针,然后指定给 f2。如果 lint -Xalias_level=weak(或更高),这将生成错误。
struct foo{ int a; int b; }; struct foo *f1; struct foo *f2; void main() { f2 = (struct foo *)&f1->b; /* cast of a scalar pointer to struct pointer error*/ } |
在以下示例中,struct fooa 类型的指针 f1 正在被强制转换为 struct foob 类型的指针。如果 lint -Xalias_level=strict(或更高),则除非结构类型相同(相同类型的相同数目的字段),否则此类强制类型转换要求显式别名。此外,在别名级别 standard 和 strong 上,假定标记必须匹配才能出现别名。在给 f1 赋值前使用 #pragma alias (struct fooa, struct foob),lint 将停止生成警告。
struct fooa { int a; }; struct foob { int b; }; struct fooa *f1; struct foob *f2; void main() { f1 = (struct fooa *)f2; /* explicit aliasing required warning */ } |
本节提供可能出现在您的源文件中的代码示例。每个示例后跟对编译器代码假定的讨论,这些假定取决于所应用的基于类型的分析级别。
考虑以下代码。可以使用不同的别名级别对它进行编译,以说明显示类型的别名关系。
struct foo { int f1; short f2; short f3; int f4; } *fp; struct bar { int b1; int b2; int b3; } *bp; int *ip; short *sp;
如果该示例是使用 -xalias_level=any 选项编译的,编译器将认为以下间接访问互为别名:
*ip、*sp、*fp、*bp、fp->f1、fp->f2、fp->f3、fp->f4、bp->b1、bp->b2、bp->b3
如果该示例是使用 -xalias_level=basic 选项编译的,编译器将认为以下间接访问互为别名:
*ip、*bp、fp->f1、fp->f4、bp->b1、bp->b2、bp->b3
另外,*sp、fp->f2 和 fp->f3 可以互为别名,*sp 和 *fp 可以互为别名。
但是,在 -xalias_level=basic 条件下,编译器作出以下假定:
*ip 不将 *sp 作为别名。
*ip 不将 fp->f2 和 fp->f3 作为别名。
*sp 不将 fp->f1、fp->f4、bp->b1、bp->b2 和 bp->b3 作为别名。
由于两个间接访问的访问类型是不同的基本类型,因此编译器作出这些假定。
如果该示例是使用 -xalias_level=weak 选项编译的,编译器将假定以下别名信息:
*ip 可将 *fp、fp->f1、fp->f4、*bp、bp->b1、bp->b2 和 bp->b3 作为别名。
*sp 可将 *fp、fp->f2 和 fp->f3 作为别名。
fp->f1 可将 bp->b1 作为别名。
fp->f4 可将 bp->b3 作为别名。
由于 f1 是结构中偏移为 0 的字段,而 b2 是结构中偏移为 4 个字节的字段,因此编译器假定 fp->fp1 不将 bp->b2 作为别名。同样,编译器假定 fp->f1 不将 bp->b3 作为别名,fp->f4 不将 bp->b1 或 bp->b2 作为别名。
如果该示例是使用 -xalias_level=layout 选项编译的,编译器将假定以下别名信息:
*ip 可将 *fp、*bp、fp->f1、fp->f4、bp->b1、bp->b2 和 bp->b3 作为别名。
*sp 可将 *fp、fp->f2 和 fp->f3 作为别名。
fp->f1 可将 bp->b1 和 *bp 作为别名。
*fp 和 *bp 可以互为别名。
由于 f4 和 b3 不是 foo 和 bar 的公共初始序列中的对应字段,因此 fp->f4 不将 bp->b3 作为别名。
如果该示例是使用 -xalias_level=strict 选项编译的,编译器将假定以下别名信息:
*ip 可将 *fp、fp->f1、fp->f4、*bp、bp->b1、bp->b2 和 bp->b3 作为别名。
*sp 可将 *fp、fp->f2 和 fp->f3 作为别名。
如果 -xalias_level=strict,则编译器假定 *fp、*bp、fp->f1、fp->f2、fp->f3、fp->f4、bp->b1、bp->b2 和 bp->b3 不互为别名,因为在忽略字段名时 foo 和 bar 是不同的。但是,fp 可将 fp->f1 作为别名,bp 可将 bp->b1 作为别名。
如果该示例是使用 -xalias_level=std 选项编译的,编译器将假定以下别名信息:
*ip 可将 *fp、fp->f1、fp->f4、*bp、bp->b1、bp->b2 和 bp->b3 作为别名。
*sp 可将 *fp、fp->f2 和 fp->f3 作为别名。
但是,fp->f1 不将 bp->b1、bp->b2 或 bp->b3 作为别名,因为在考虑字段名时 foo 和 bar 并不相同。
如果该示例是使用 -xalias_level=strong 选项编译的,编译器将假定以下别名信息:
*ip 不将 fp->f1、fp->f4、bp->b1、bp->b2 和 bp->b3 作为别名,因为指针(如 *ip)不应指向结构内部。
同样,*sp 不将 fp->f1 或 fp->f3 作为别名。
由于类型不同,*ip 不将 *fp、*bp 和 *sp 作为别名。
由于类型不同,*sp 不将 *fp、*bp 和 *ip 作为别名。
考虑以下源代码示例。当使用不同的别名级别编译时,它说明显示的类型的别名关系。
struct foo { int f1; int f2; int f3; } *fp; struct bar { int b1; int b2; int b3; } *bp;
如果该示例是使用 -xalias_level=any 选项编译的,编译器将假定以下别名信息:
*fp、*bp、fp->f1、fp->f2、fp->f3、bp->b1、bp->b2 和 bp->b3 都可以互为别名,因为任何两个内存访问在 -xalias_level=any 级别上可互为别名。
如果该示例是使用 -xalias_level=basic 选项编译的,编译器将假定以下别名信息:
*fp、*bp、fp->f1、fp->f2、fp->f3、bp->b1、bp->b2 和 bp->b3 都可以互为别名。在本示例中,由于所有结构字段均为同一基本类型,因此任何两个使用指针 *fp 和 *bp 的字段访问都可以互为别名。
如果该示例是使用 -xalias_level=weak 选项编译的,编译器将假定以下别名信息:
*fp 和 *fp 可以互为别名。
fp->f1 可将 bp->b1、*bp 和 *fp 作为别名。
fp->f2 可将 bp->b2、*bp 和 *fp 作为别名。
fp->f3 可将 bp->b3、*bp 和 *fp 作为别名。
但是,-xalias_level=weak 强加以下限制:
由于 f1 的偏移为零,与 b2 的偏移(四字节)和 b3 的偏移(八字节)不同,因此 fp->f1 不将 bp->b2 或 bp->b3 作为别名。
由于 f2 的偏移为四字节,与 b1 的偏移(零字节)和 b3 的偏移(八字节)不同,因此 fp->f2 不将 bp->b1 或 bp->b3 作为别名。
由于 f3 的偏移为八字节,与 b1 的偏移(零字节)和 b2 的偏移(四字节)不同,因此 fp->f3 不将 bp->b1 或 bp->b2 作为别名。
如果该示例是使用 -xalias_level=layout 选项编译的,编译器将假定以下别名信息:
*fp 和 *bp 可以互为别名。
fp->f1 可将 bp->b1、*bp 和 *fp 作为别名。
fp->f2 可将 bp->b2、*bp 和 *fp 作为别名。
fp->f3 可将 bp->b3、*bp 和 *fp 作为别名。
但是,-xalias_level=layout 强加以下限制:
由于在 foo 和 bar 的公共初始序列中字段 f1 对应于字段 b1,因此 fp->f1 不将 bp->b2 或 bp->b3 作为别名。
由于在 foo 和 bar 的公共初始序列中字段 f2 对应于字段 b2,因此 fp->f2 不将 bp->b1 或 bp->b3 作为别名。
由于在 foo 和 bar 的公共初始序列中字段 f3 对应于字段 b3,因此 fp->f3 不将 bp->b1 或 bp->b2 作为别名。
如果该示例是使用 -xalias_level=strict 选项编译的,编译器将假定以下别名信息:
*fp 和 *bp 可以互为别名。
fp->f1 可将 bp->b1、*bp 和 *fp 作为别名。
fp->f2 可将 bp->b2、*bp 和 *fp 作为别名。
fp->f3 可将 bp->b3、*bp 和 *fp 作为别名。
但是,-xalias_level=strict 强加以下限制:
由于在 foo 和 bar 的公共初始序列中字段 f1 对应于字段 b1,因此 fp->f1 不将 bp->b2 或 bp->b3 作为别名。
由于在 foo 和 bar 的公共初始序列中字段 f2 对应于字段 b2,因此 fp->f2 不将 bp->b1 或 bp->b3 作为别名。
由于在 foo 和 bar 的公共初始序列中字段 f3 对应于字段 b3,因此 fp->f3 不将 bp->b1 或 bp->b2 作为别名。
如果该示例是使用 -xalias_level=std 选项编译的,编译器将假定以下别名信息:
fp->f1、fp->f2、fp->f3、bp->b1、bp->b2 和 bp->b3 不互为别名。
如果该示例是使用 -xalias_level=strong 选项编译的,编译器将假定以下别名信息:
fp->f1、fp->f2、fp->f3、bp->b1、bp->b2 和 bp->b3 不互为别名。
考虑下列源代码示例,它说明某些别名级别无法处理内部指针。有关内部指针的定义,请参见表 B–13。
struct foo { int f1; struct bar *f2; struct bar *f3; int f4; int f5; struct bar fb[10]; } *fp; struct bar struct bar *b2; struct bar *b3; int b4; } *bp; bp=(struct bar*)(&fp->f2);
weak、layout、strict 或 std 不支持该示例中的非关联化。在指针赋值 bp=(struct bar*)(&fp->f2) 之后,以下各对内存访问将访问相同的存储单元:
fp->f2 和 bp->b2 访问相同的存储单元
fp->f3 和 bp->b3 访问相同的存储单元
fp->f4 和 bp->b4 访问相同的存储单元
但是,使用选项 weak、layout、strict 和 std 时,编译器假定 fp->f2 和 bp->b2 不设定别名。由于 b2 的偏移为零,与 f2 的偏移(四字节)不同,并且 foo 和 bar 没有公共初始序列,因此编译器作出该假定。同样,编译器还假定 bp->b3 不将 fp->f3 作为别名,bp->b4 不将 fp->f4 作为别名。
因此,指针赋值 bp=(struct bar*)(&fp->f2) 将使编译器关于别名信息的假定不正确。这可能会导致不正确的优化。
请在进行以下示例中显示的修改之后尝试编译。
struct foo { int f1; struct bar fb; /* Modified line */ #define f2 fb.b2 /* Modified line */ #define f3 fb.b3 /* Modified line */ #define f4 fb.b4 /* Modified line */ int f5; struct bar fb[10]; } *fp; struct bar struct bar *b2; struct bar *b3; int b4; } *bp; bp=(struct bar*)(&fp->f2); |
在指针赋值 bp=(struct bar*)(&fp->f2) 之后,以下各对内存访问将访问相同的存储单元:
fp->f2 和 bp->b2
fp->f3 和 bp->b3
fp->f4 和 bp->b4
通过检查前面的代码示例中显示的更改,您可以看到表达式 fp->f2 是表达式 fp->fb.b2 的另一种形式。由于 fp->fb 为 bar 类型,因此 fp->f2 访问 bar 的 b2 字段。此外,bp->b2 也访问 bar 的 b2 字段。因此,编译器假定 fp->f2 将 bp->b2 作为别名。同样,编译器假定 fp->f3 将 bp->b3 作为别名,fp->f4 将 bp->b4 作为别名。结果,编译器假定的别名与指针赋值产生的实际别名匹配。
考虑以下源代码示例。
struct foo { int f1; int f2; } *fp; struct bar { int b1; int b2; } *bp; struct cat { int c1; struct foo cf; int c2; int c3; } *cp; struct dog { int d1; int d2; struct bar db; int d3; } *dp;
如果该示例是使用 -xalias_level=weak 选项编译的,编译器将假定以下别名信息:
fp->f1 可将 bp->b1、cp->c1、dp->d1、cp->cf.f1 和 df->db.b1 作为别名。
fp->f2 可将 bp->b2、cp->cf.f1、dp->d2、cp->cf.f2、df->db.b2、cp->c2 作为别名。
bp->b1 可将 fp->f1、cp->c1、dp->d1、cp->cf.f1 和 df->db.b1 作为别名。
bp->b2 可将 fp->f2、cp->cf.f1、dp->d2、cp->cf.f1 和 df->db.b2 作为别名。
fp->f2 可将 cp->c2 作为别名,因为 *dp 可将 *cp 作为别名,且 *fp 可将 dp->db 作为别名。
cp->c1 可将 fp->f1、bp->b1、dp->d1 和 dp->db.b1 作为别名。
cp->cf.f1 可将 fp->f1、fp->f2、bp->b1、bp->b2、dp->d2 和 dp->d1 作为别名。
cp->cf.f1 不将 dp->db.b1 作为别名。
cp->cf.f2 可将 fp->f2、bp->b2、dp->db.b1 和 dp->d2 作为别名。
cp->c2 可将 dp->db.b2 作为别名。
cp->c2 不将 dp->db.b1 作为别名,cp->c2 不将 dp->d3 作为别名。
对于偏移,仅当 *dp 将 cp->cf 作为别名时,cp->c2 才能将 db->db.b1 作为别名。但是,如果 *dp 将 cp->cf 作为别名,则 dp->db.b1 必须在 foo cf 末尾之后设定别名,这是对象约束所禁止的。因此,编译器假定 cp->c2 不能将 db->db.b1 作为别名。
cp->c3 可将 dp->d3 作为别名。
请注意,cp->c3 不将 dp->db.b2 作为别名。由于具有非关联化所涉及的类型的字段的偏移不同并且不重叠,因此这些内存引用不设定别名。基于这种情况,编译器假定它们不能设定别名。
dp->d1 可将 fp->f1、bp->b1 和 cp->c1 作为别名。
dp->d2 可将 fp->f2、bp->b2 和 cp->cf.f1 作为别名。
dp->db.b1 可将 fp->f1、bp->b1 和 cp->c1 作为别名。
dp->db.b2 可将 fp->f2、bp->b2、cp->c2 和 cp->cf.f1 作为别名。
dp->d3 可将 cp->c3 作为别名。
请注意,dp->d3 不将 cp->cf.f2 作为别名。由于具有非关联化所涉及的类型的字段的偏移不同并且不重叠,因此这些内存引用不设定别名。基于这种情况,编译器假定它们不能设定别名。
如果该示例是使用 -xalias_level=layout 选项编译的,编译器只假定以下别名信息:
fp->f1、bp->b1、cp->c1 和 dp->d1 都可以互为别名。
fp->f2、bp->b2 和 dp->d2 都可以互为别名。
fp->f1 可将 cp->cf.f1 和 dp->db.b1 作为别名。
bp->b1 可将 cp->cf.f1 和 dp->db.b1 作为别名。
fp->f2 可将 cp->cf.f2 和 dp->db.b2 作为别名。
bp->b2 可将 cp->cf.f2 和 dp->db.b2 作为别名。
如果该示例是使用 -xalias_level=strict 选项编译的,编译器只假定以下别名信息:
fp->f1 和 bp->b1 可互为别名。
fp->f2 和 bp->b2 可互为别名。
fp->f1 可将 cp->cf.f1 和 dp->db.b1 作为别名。
bp->b1 可将 cp->cf.f1 和 dp->db.b1 作为别名。
fp->f2 可将 cp->cf.f2 和 dp->db.b2 作为别名。
bp->b2 可将 cp->cf.f2 和 dp->db.b2 作为别名。
如果该示例是使用 -xalias_level=std 选项编译的,编译器只假定以下别名信息:
fp->f1 可将 cp->cf.f1 作为别名。
bp->b1 可将 dp->db.b1 作为别名。
fp->f2 可将 cp->cf.f2 作为别名。
bp->b2 可将 dp->db.b2 作为别名。
考虑以下源代码示例。
struct foo { short f1; short f2; int f3; } *fp; struct bar { int b1; int b2; } *bp; union moo { struct foo u_f; struct bar u_b; } u;
下面是编译器根据以下别名级别作出的假定:
如果该示例是使用 -xalias_level=weak 选项编译的,fp->f3 和 bp->b2 可互为别名。
如果该示例是使用 -xalias_level=layout 选项编译的,没有字段可以互为别名。
如果该示例是使用 -xalias_level=strict 选项编译的,fp->f3 和 bp->b2 可互为别名。
如果该示例是使用 -xalias_level=std 选项编译的,没有字段可互为别名。
考虑以下源代码示例。
struct bar; struct foo { struct foo *ffp; struct bar *fbp; } *fp; struct bar { struct bar *bbp; long b2; } *bp;
下面是编译器根据以下别名级别作出的假定:
如果该示例是使用 -xalias_level=weak 选项编译的,只有 fp->ffp 和 bp->bbp 可互为别名。
如果该示例是使用 -xalias_level=layout 选项编译的,只有 fp->ffp 和 bp->bbp 可互为别名。
如果该示例是使用 -xalias_level=strict 选项编译的,则没有字段可以互为别名,原因是即使删除两种结构类型的标记,两种结构类型仍不相同。
如果该示例是使用 -xalias_level=std 选项编译的,则没有字段可以互为别名,原因是两种类型和标记均不相同。
考虑以下源代码示例:
struct foo; struct bar; #pragma alias (struct foo, struct bar) struct foo { int f1; int f2; } *fp; struct bar { short b1; short b2; int b3; } *bp;
此示例中的 pragma 告知编译器,允许 foo 和 bar 互为别名。编译器作出关于别名信息的以下假定: