PC-lint 错误码大全
目录
- 19.1 语法错误
- 19.2 内部错误
- 19.3 致命错误
- 19.4 警告信息
- 19.5 信息性消息
- 19.6 可选注释
大多数错误消息都有一个关联的错误编号。通过查找下面的编号,您可以获得关于错误原因的更多信息。该信息还可以通过一个自解压可执行文件 msg.exe
获得,该文件位于 PC-lint 分发磁盘上,它会生成机器可读的 ASCII 文件 msg.txt
。对于 FlexeLint 用户,文件 msg.txt
可以在 FlexeLint 补充磁盘上找到。
编号为 1000 及以上的消息通常与 C++ 相关。此文档仅仅涉及C相关的内容。这在下表中有所总结。
在可能的 1000 被减去后,余数位于 0-999 范围内。余数在 1-199 范围内是语法错误,200-299 是 PC-lint/FlexeLint 内部错误,通常不应该发生,300-399 是致命错误,通常是由于超出某些限制引起的,400-699 是警告消息,表示程序可能存在问题。余数在 700-899 范围内表示信息性消息。这些可能是错误,但也可能根据个人编程风格代表合法的编程实践。余数在 900-999 范围内的被称为“可选注释”。它们不会自动输出。您可以查看列表,决定是否希望被提醒这些注释。
C 语言的消息编号和警告等级对比表
类别 | C 语言 | 警告等级 |
---|---|---|
语法错误 | 1 - 199 | 1 |
内部错误 | 200 - 299 | 0 |
致命错误 | 300 - 399 | 0 |
警告消息 | 400 - 699 | 2 |
信息性消息 | 700 - 899 | 3 |
可选注释 | 900 - 999 | 4 |
术语表
- 参数(argument):函数的实际参数,与函数的形式参数(或称为形参)相对(参见 parameter)。
- 算术类型(arithmetic):包括整数类型(见下文)以及
float
、double
和long double
。 - 布尔类型(Boolean):一般来说,布尔类型指可以为真或假的量。一个表达式如果形式为
operand op operand
(op
是关系运算符、相等运算符、逻辑与或逻辑或),则该表达式被称为布尔表达式。如果该表达式用于if
或while
语句中,或者是for
语句中的第二个表达式,或者是传递给逻辑与或逻辑或运算符的参数,则该上下文被称为需要布尔类型的上下文。在需要布尔类型的上下文中,任何整数或指针类型都是可接受的。 - 声明(declaration):提供关于对象或函数的属性信息(与定义相对)。
- 定义(definition):分配对象或函数的存储空间(与声明相对),并可能指示该对象的属性。一个对象只能有一个定义,但可以有多个声明。
- 整数类型(integral):具有类似于整数的属性的类型。这些类型包括
char
、short
、int
和long
以及它们的无符号版本。 - 标量类型(scalar):包括所有算术类型和指针类型。
- 左值(lvalue):可以出现在赋值操作符左侧的表达式。一些上下文要求左值,例如自增(
++
)和自减(--
)。 - 宏(macro):由
#define
语句定义的缩写。它可以带有参数,也可以没有。 - 成员(member):结构体(
struct
)和联合(union
)的元素称为成员。 - 模块(module):由编译器在一次独立编译中编译的所有内容。通常包括
.c
(或.cpp
、.cxx
等)文件的所有文本及任何#include
文件中的文本。 - 形式参数(parameter):与实际参数相对的函数的形式参数(参见 argument)。
语法错误
- 未闭合的注释(位置):到达文件末尾时,一个打开的注释仍未闭合。消息中显示了未闭合注释的起始位置。
- 未闭合的引号:到达行尾时,未找到与前面的引号字符(单引号或双引号)匹配的引号字符。
- #else 没有 #if:遇到 #else 时,不在 #if、#ifdef 或 #ifndef 的范围内。
- #if 级别过多:#if 语句(包括 #ifdef 和 #ifndef)嵌套级别超出了内部限制。
- #endif 数量过多:遇到 #endif 时,不在 #if、#ifdef 或 #ifndef 的范围内。
- 堆栈溢出:内置的不可扩展堆栈已超限。可能的原因包括嵌套的 #if 语句过多、#include 语句(包括所有递归 #include 语句)过多、静态代码块(由花括号界定)或 #define 替换过多。
- 无法打开包含文件:文件名:无法打开指定的包含文件。
- 未闭合的 #if(位置):遇到 #if(或 #ifdef 或 #ifndef)时,没有对应的 #endif。消息中显示了 #if 的位置。
- #if 中的 #else 过多(位置):某个 #if 包含了一个 #else,随后又跟着另一个 #else 或 #elif。错误消息中提供了包含错误的条件语句的 #if 语句的行号。
- 期望 ‘字符串’:期望的标记未找到。通常发生在某些保留字未被识别时。
int __interrupt f();
将在 f 处收到一个期望 ';'
的消息,因为它认为您刚刚声明了 __interrupt。解决方法是使用 +rw(__interrupt) 建立一个新的保留字。此外,确保您使用了正确的编译器选项文件。
-
大小过大:#include 行中指定的文件名长度超过了 FILENAME_MAX 字符数。
-
需要 < 或 ":在检测到 #include 之后,并且在宏替换执行之后,期望文件规范的格式为
<文件名>
或"文件名"
。 -
类型错误:无法将类型形容词(如
long
、unsigned
等)应用于后面的类型。 -
符号 ‘Symbol’ 已在之前定义(位置):命名的对象已被第二次定义。提供了之前定义的位置。如果这是一个暂时的定义(没有初始化器),则可以使用 +fmd 标志来抑制此消息。
-
符号 ‘Symbol’ 重新声明(类型差异)(位置):命名的符号在其他模块中(提供位置)已经以与当前声明不同的类型声明或定义。参数
TypeDiff
提供了关于类型差异的进一步信息。 -
无法识别的名称:一个
#
指令没有跟随一个可识别的单词。如果这不是错误,可以使用+ppw
选项。 -
无法识别的名称:在只应声明参数的地方声明了一个非参数。
-
符号 ‘Symbol’ 重新声明(类型差异)与位置冲突:一个符号正在重新声明。参数
TypeDiff
提供了关于类型差异的进一步信息。位置是前一个定义的位置。 -
无用的声明:一个类型单独出现而没有关联的变量,并且该类型既不是
struct
、union
也不是enum
。双重分号(如int x;;
)可能会引发此错误。 -
非法使用 =:函数声明后跟一个
=
号。 -
期望 {:一个不定长数组的初始化器必须以左花括号
{
开始。 -
非法操作符:在操作数后发现了一个一元操作符,但该操作符不是后缀操作符。
-
期望冒号:遇到一个
?
操作符,但后面没有跟随一个:
。 -
期望表达式,找到 ‘字符串’:在表达式开始的地方发现了一个操作符,但它不是一元操作符。
-
非法常量:在字符常量(用
'
包围的常量)中遇到了过多字符。 -
期望表达式,找到 ‘字符串’:在期望表达式的地方没有找到表达式,消息中显示了意外的标记。
-
非法字符 (0xhh):在源代码中发现了非法字符。消息中提供了该字符的十六进制代码。假定它是一个空格。如果您在标识符名称中使用了奇怪的字符,将会收到此消息,可以使用
-ident
选项。 -
符号 ‘Symbol’ 的重新定义(位置):标识符在冒号之前已经在给定位置被声明为不是标签。
-
期望常量:期望一个常量但未获得。可能是出现在
case
关键字后、数组维度、位域长度、枚举值、#if
表达式等之后。 -
符号 ‘Symbol’ 的重新定义与位置冲突:在本模块中之前定义的数据对象或函数被重新定义。
-
字段大小(成员 ‘Symbol’)不应为零:字段的长度被给定为非正值(0 或负值)。
-
非法常量:常量的格式不正确,例如八进制常量包含 8 或 9 数字。
-
非常量初始化器:静态数据项的初始化器是非常量。
-
初始化器有副作用:静态数据项的初始化器包含副作用。
-
符号 ‘Symbol’ 的存储类重新定义与位置冲突:对象的存储类正在被更改。
-
枚举成员 ‘Symbol’ 的值不一致(与位置冲突):枚举成员的值不一致。
-
符号 ‘Symbol’ 的偏移量不一致(位置):类或结构体的成员在起始位置(从结构体的开头计算的偏移量)与早先的声明不同。可能是由于数组维度在一个模块中发生了变化。
-
符号 ‘Symbol’ 的重新定义与位置冲突:结构体或联合体正在重新定义。
-
未声明的标识符 ‘名称’:在表达式中遇到的标识符既没有被先前声明,也没有跟随一个左括号
(
。消息中提供了标识符的名称。 -
符号 ‘Symbol’ 的重新定义:函数或宏的参数正在重复。
-
期望语句:期望语句,但遇到了不可能开始语句的标记。
-
变量 ‘Symbol’ 的无效类型:发现了无效类型,例如在需要具体类型的上下文中使用了
void
类型。 -
需要 switch:在没有
switch
语句的情况下出现了case
或default
语句。 -
不正确的
register
使用:变量被声明为register
,但其类型与register
不一致(例如函数)。 -
字段类型应为
int
:结构体中的位域应为unsigned
或int
类型。如果您的编译器允许其他类型(如char
),可以抑制此消息。 -
类型错误:一元减号
-
需要一个算术操作数。 -
类型错误:一元
*
或指针操作符->
的左侧需要一个指针操作数。 -
期望类型:在原型中只允许类型。原型是一个函数声明,其中括号内包含一系列类型。此时,处理器已经检测到括号内至少有一个类型,因此期望更多类型或右括号
)
。 -
尝试获取非左值的地址:一元
&
操作符需要一个左值(适合用于赋值操作符左侧的值)。 -
期望整型:一元
~
操作符需要一个整型操作数(有符号或无符号char
、short
、int
或long
)。 -
期望左值:自减
--
和自增++
操作符需要一个左值(适合用于赋值操作符左侧的值)。请注意,类型转换通常不会产生左值。因此,
++(char *)p;
根据 ANSI 标准是非法的。一些编译器允许此构造,如果您想使用,可以使用 +fpc
选项(指针转换为左值)。
- 期望标量:自减
--
和自增++
操作符只能应用于标量(算术类型和指针)或对这些操作符有定义的对象。 - 除以 0:在除法
/
或取余%
操作符的右侧使用了常量 0。 - 类型错误:上下文要求一个标量、函数、数组或结构体类型。
- 类型错误:加减操作符要求标量类型,指针不能相加。
- 类型错误:位运算符
&
、|
和^
需要整型操作数。 - 类型错误:关系运算符的参数不正确,这些运算符总是需要两个标量操作数,指针不能与整数进行比较(除非是常量 0)。
- 类型错误:位移量必须是整型。
- 类型错误:需要整型的位移操作数。
- 类型错误:上下文要求布尔值。布尔值必须是某种形式的算术类型或指针类型。
- 操作符
:
的类型不兼容(类型差异):?:
操作符的第二个和第三个参数必须是兼容类型。 - 期望左值:赋值操作符要求第一个操作数为左值。请注意,类型转换会移除表达式的左值属性。
- 类型不匹配(上下文)(类型差异):在赋值(或隐含赋值,参见上下文)中类型不匹配。类型差异由
TypeDiff
指定。 - 期望成员名称:在
.
或->
操作符之后应出现成员名称。 - 类型错误:在不允许的地方使用了
void
类型。如果在原型中使用void
类型,那么它必须是原型中唯一的类型。(参见错误编号 49。) - 无法从
Type
到Type
的类型转换:尝试将非标量类型转换为整型。 - 无法从
Type
到Type
的类型转换:尝试将非算术类型转换为浮点数。 - 无法从
Type
到Type
的类型转换:涉及不兼容的结构体或结构体与其他对象之间的错误转换。 - 无法从
Type
到Type
的类型转换:尝试将异常类型(非整型)转换为指针。 - 无法从
Type
到Type
的类型转换:尝试转换为不允许转换的类型。 - 错误的选项 ‘字符串’:无法解释选项。消息中显示了选项的内容。
- 错误的左操作数:光标位于或刚超过
->
或.
操作符。该操作符期望其左侧为一个主表达式。请将此位置的任何复杂表达式括在括号中。 - 寄存器地址:尝试对存储类别为
register
的变量应用地址操作符&
。 - 更改尺寸(选项 ‘字符串’)为时已晚:尺寸选项在处理模块的全部或部分后给出。确保在处理第一个模块或在处理任何模块之前在线命令中提供重置对象尺寸的选项。
- 无法打开文件 ‘字符串’:消息中显示了文件的名称。指定的文件无法打开用于输出。该文件旨在成为 PC-lint/FlexeLint 对象模块。
- 无法获取位字段的地址:无法获取位字段的地址。C 语言的规则只允许获取完整字节(完整字符)的地址。
- 符号 ‘Symbol’ 在位置定义为
typedef
,用于表达式中:命名的符号在typedef
语句中被定义,因此被视为类型。随后在一个期望表达式的上下文中发现了它。 - % 操作符的类型错误:% 操作符应与某种形式的整型一起使用。
- 省略号的使用不严格符合 ANSI 标准:省略号应仅在类型序列之后出现在原型中,而不应在标识符序列之后。一些编译器支持此扩展。如果您想使用此功能,请抑制此消息。
- 在相等比较中不允许
struct/union
:两个结构体或联合体正在使用==
或!=
比较。这不符合 ANSI 标准。如果您的编译器支持此功能,请抑制此消息。 - 在
void
函数中使用return <exp>;
是非法的:ANSI 标准不允许在void
函数中使用表达式形式的return
语句。如果您尝试将return
转换为void
,例如return (void)f();
,并且您的编译器允许,请抑制此消息。 - 在减法中不兼容的指针类型:两个指针的间接类型不同,被减去。您可以通过使用一个或多个
-ep...
选项来忽略指针之间的轻微差异。 - 对象的 sizeof 值为零或对象未定义:
sizeof
返回了 0 值。这可能是因为对象未定义或定义不完整。确保在使用sizeof
时,对象的完整定义在作用域内。 - 数组 ‘Symbol’ 的维度为 0:在需要非零维度的上下文中声明了一个没有维度的数组(命名为
符号
)。 - 结构体 ‘Symbol’ 没有数据元素:声明了一个没有数据成员的结构体(在 C 模块中)。尽管在 C++ 中合法,但在 C 中不合法。
#ifdef
或#ifndef
表达式过于复杂:根据 C 语言规则,在#ifdef
或#ifndef
之后应该只有一个标识符。您还可以提供一个合法构造的 C(或 C++)注释。- 符号 ‘Symbol’ 是一个空元素数组:在 C 模块中声明了一个元素长度为 0 的数组。尽管在 C++ 中合法,但在 C 中不允许。
- 参数或选项过长(‘字符串’):选项的长度(如消息中显示的字符串)超过了内部限制。请尝试将选项分解成较小的部分。截至本书写时,限制为 610 个字符。
- 选项 ‘字符串’ 仅适用于 lint 注释内:指定的选项在命令行或
.lnt
级别不适用。例如,如果在命令行上给出了-unreachable
,您将收到此消息。 - 行超过了整数个字符(使用 +linebuf):从一个输入文件读取的行长于预期。默认行缓冲区大小为 600 个字符。每次使用
+linebuf
选项时,您可以将该大小加倍。该大小可以无限期加倍。 - 数组维度或位域长度为负(整数):不允许负数组维度或位域长度。
- 在宏的字符串参数中不允许换行符:宏调用包含了跨越多行的字符串。例如:
A( "Hello
World" );
这种情况将触发此消息。一些编译器接受这种构造,您可以抑制此消息(如果这是您的当前做法)。但更便携的做法是将字符串常量放在一行上。例如:
A( "Hello World" );
会更好。
-
期望宏参数但找到 ‘名称’:在宏定义中找到
#
操作符(或非标准的#@
操作符扩展),但它没有立即跟随宏的参数,这是标准所要求的。名称
标识了操作符右侧的标记。 -
左花括号
{
不匹配:此消息的目的是报告一个左花括号{
未匹配到相应的右花括号}
的位置。这样的未匹配左花括号可能与检测到的不平衡点(通常是编译单元的结尾)相隔甚远。提供左花括号的位置对于确定不平衡的来源非常有帮助。 -
恢复错误(字符串):在尝试从语法错误中恢复时发现不一致状态时发出的恢复错误消息。消息中提供的字符串表示这种不一致状态的线索。由于错误的原因很可能是之前的错误,应优先解决原始错误。此“恢复错误”仅用于提供有关解析器状态的附加信息。
-
期望标识符:在处理函数声明时,遇到的参数说明不是标识符,而先前的参数被指定为标识符。这是新旧风格函数声明混用的情况,不被允许。例如:
c void f(n, int m);
会引发此消息。 -
非法参数说明:在函数声明中,参数必须被指定为标识符或作为类型后跟声明符。
-
意外声明:在原型之后,只有逗号、分号、右括号或左花括号可能出现。如果您遗漏了声明后的终止字符,或混用了旧风格参数声明与新风格原型,可能会发生此错误。
-
冲突的类型:发现了两个连续冲突的类型,如
int
后跟double
。删除其中一个类型! -
冲突的修饰符:发现了两个连续冲突的修饰符,如
far
后跟near
。删除其中一个修饰符! -
非法常量:在预处理器表达式中发现了字符串常量,如:
c #if ABC == "abc"
这种表达式应为整型表达式。 -
标签 ‘Symbol’(位置)未定义:在
goto
语句中出现的符号没有对应的标签。 -
上下文不正确:在没有适当的包围上下文(如
for
、while
或do
循环,或switch
语句对于break
)的情况下遇到了continue
或break
语句。 -
short long
组合不是标准的,假定为long
:一些编译器支持非标准的short long
组合。这条消息报告了使用此组合作为错误。如果您需要使用该构造,请抑制此消息。如消息所述,该类型将被假定为long
。 -
尝试对
void
进行赋值:尝试对一个被标记为void
的对象进行赋值(可能通过指针)。 -
对
const
对象赋值:一个声明为const
的对象被赋值。这可能通过间接引用引发。例如,如果p
是指向const int
的指针,则对*p
进行赋值会引发此错误。 -
枚举声明不一致:枚举中的成员序列(或它们的值)与具有相同名称的另一个枚举(通常在其他模块中)不一致。
-
结构声明 ‘Symbol’ 不一致:结构体(或联合体)中的成员序列与另一个具有相同名称的结构体(通常在其他模块中)不一致。
-
结构体/联合体未定义:引用了一个结构体或联合体,但其定义未在当前作用域内。例如,引用
p->a
,其中p
是指向尚未在当前模块中定义的结构体的指针。 -
不适当的存储类:在代码的参数声明部分(在函数的第一个左花括号之前)中使用了
register
之外的存储类。 -
不适当的存储类:在任何函数之外指定了存储类,但该存储类指示
auto
或register
。这些存储类仅适用于函数内。 -
原型的参数过少:为函数提供的参数数量少于作用域中的原型所指示的数量。
-
原型的参数过多:为函数提供的参数数量多于作用域中的原型所指示的数量。
-
未使用大括号初始化无数据类型 ‘Symbol’:尝试在没有大括号的情况下初始化嵌套对象(如数组元素),并且该对象类型不包含数据成员。
class A { public: void f(); }; class B { public: A a; int k; } ; A a[4] = { {}, {}, {}, {} }; // OK B b = { , 34 }; // Error 120
-
尝试初始化未定义类型 ‘Symbol’ 的对象:尝试初始化一个没有可见定义的对象。例如:
c class Undefined u = { 5 };
-
数字(字符)超出进制范围:在以零开头的常量中发现了非法字符。例如,
08
被一些编译器接受为表示8
,但它应该是010
或简单的8
。 -
宏 ‘Symbol’ 在位置处以参数定义,但后续未跟随 ‘(’,这仅是警告:定义了带参数的宏名,随后使用该宏名时未跟随
'('
。这是合法的,但可能是疏忽。常见的做法是抑制此消息(使用-e123
),因为一些编译器允许宏max()
与变量max
共存。 -
不允许
void
指针:在不允许void
的上下文中使用了void
指针。这包括减法、加法和关系运算符(>
、>=
、<
、<=
)。 -
存储类说明符过多:发现了一个或多个存储类说明符(如
static
、extern
、typedef
、register
或auto
)。只能允许一个。 -
结构体定义 ‘Symbol’ 不一致:命名的结构体(或联合体或枚举)在各模块之间定义不一致。在处理 lint 对象模块时识别出此不一致。此消息未提供行号信息。修改结构以使成员信息一致。
-
非法常量:发现了空字符常量
''
。 -
不允许的函数指针:在算术上下文(如减法、加法或关系运算符
>
、>=
、<
、<=
)中使用了函数指针。 -
声明预期,标识符 ‘Symbol’ 被忽略:在期望声明的上下文中发现了标识符。此外,标识符未跟随
(
或[
。 -
期望整型:
switch
语句中的表达式必须是某种变体的int
(可能是long
或unsigned
)或枚举类型。 -
调用宏 ‘Symbol’ 时的语法错误,位置处:此消息在带参数的宏(函数式宏)调用时发出,提供的参数数量不正确。位置是宏调用的起始位置。由于错误的宏调用可能会跨越多行,因此提供该信息非常有用。
-
期望函数定义:带有括号中的标识符的函数声明是旧式函数定义(K&R 风格)的开始。通常紧随其后的是可选的声明和一个左花括号,以表示函数体的开始。要么用类型替换标识符,要么用函数体完成函数。
-
聚合 ‘Symbol’ 的初始化器过多:在大括号包围的初始化器中,项目多于聚合的元素数量。
-
缺少初始化器:期望一个初始化器,但只看到逗号。
-
初始化器中假定为逗号:两个初始化器之间缺少逗号。例如:
c int a[2][2] = { { 1, 2 } { 3, 4 } };
在第一个右花括号}
之后缺少逗号。 -
非法宏名称:ANSI 标准限制使用某些名称作为宏名,
defined
就在限制名单中。 -
常量 ‘Symbol’ 在 switch 语句中被使用了两次:在 switch 语句中两次使用了相同的常量。目前只检查枚举类型是否重复出现。
-
无法将父类型 ‘Symbol’ 添加到强类型 ‘字符串’ 中;会创建循环:尝试将强类型父类型添加到 typedef 类型中。该尝试可以是显式的(使用 -strong 选项),也可以是通过使用已知强类型的 typedef 隐式进行的。这种尝试会导致强类型父子关系中出现循环。而这种循环是不可容忍的。
-
无法获取函数的 sizeof:尝试获取一个函数的 sizeof。
-
修饰符后跟类型:Microsoft 修饰符(如 far、_near、__huge、_pascal 等)修饰其右侧的声明符,因此不应出现在类型之前。例如,您应该写
int pascal f(void);
,而不是pascal int f(void);
。请注意,const
和volatile
与 Microsoft 修饰符不同,它们可以出现在类型之前或之后。报告错误后,尝试按程序员可能的意图处理修饰符。 -
以下选项元素过多:‘字符串’:指定的选项(由
字符串
给出)太大。它很可能包含了一个元素列表,该列表的项目过多。您应将大选项分解为两个或更多小选项,这些小选项加起来等效于一个大选项。 -
错误选项:字符串:选项包含的信息与自身或先前的选项不一致。消息中的字符串更详细地解释了问题所在。
-
符号 ‘Symbol’ 的返回值不存在,与位置比较:尝试使用命名函数的不存在返回值(由符号标识)。之前已经决定函数不返回值或被声明为
void
。 -
操作符前应有类型,假定为 void:在期望类型的上下文中未找到类型,而是遇到了操作符
*
或&
。假定此操作符之前是void
。 -
假定为二进制常量:遇到格式为
0b...
的常量。假定这是一个二进制常量。例如,0b100
代表值 4。如果您的编译器支持二进制常量,可以抑制此消息。 -
sizeof 只接受一个参数:检测到形式为
sizeof(a,b)
的表达式。第二个参数不符合标准,有些编译器将其作为 sizeof 操作符的选项使用。如果您的编译器对第二个参数有用,可以抑制此消息。 -
成员 ‘Symbol’ 在位置已被声明:指定的成员已在同一结构体或联合体内声明。尽管重新声明一个函数看起来无害,但这在语言规则中是不允许的。应删除其中一个声明。
-
在 C 代码中发现 C++ 构造 ‘字符串’:在 C 代码中发现了一个非法构造。它看起来适合 C++。消息中的字符串进一步标识了该构造。
-
标记 ‘字符串’ 意外出现,字符串:遇到了一个意外的标记。消息中第二个参数标识了采取的操作(如果有的话)。
-
标记 ‘名称’ 与抽象类型不一致:在允许抽象类型的上下文中(例如在强制转换中或在 sizeof 之后),在开始解析抽象类型之后遇到了标识符。例如:
c x = (int y) z;
-
丢失的基文件 ‘文件名’:指定的文件通过选项
-lobbase()
指定为 lob 生产的基础。在输出时,如果丢失了 lob 基础文件,则会给出此消息。此问题可以通过生成丢失的 lob 输出来纠正。只要在 make 文件中有适当的依赖关系,这就不成问题。在输入时,此消息最可能的原因是基文件过时。正在读取的 lob 文件中的哈希码与基文件中嵌入的相似代码不匹配。输入的 lob 文件应视为错误,应重新生成。 -
无法创建临时文件:生成基于某个 lob 基础文件的 lob 输出文件时,会发出此消息。生成 lob 文件时,首先将其写入临时文件。临时文件由 C 库函数
tmpnam()
生成。 -
无法评估类型 ‘字符串’,假定为 int:消息中的字符串是
printf_code
选项或scanf_code
选项的第二个参数。在使用时,它应被评估为一种类型。不幸的是,未能识别该类型。 -
忽略表达式中的 { },假定为 0:一些编译器支持将看似复合语句的内容作为 C/C++ 表达式。例如,要定义一个仅被读取一次的整数的绝对值,可以使用:
c #define abs(a) { int b = a; b >= 0 ? b : -b; }
列表中的最后一个表达式是结果。为了在语法上支持此构造而不出错,我们识别了该序列并发出了此消息。如果您希望使用此功能,只需抑制消息。 -
标量类型 ‘名称’ 的大括号初始化器:会引发此消息的初始化器示例如下:
c int s[] = { { 1 } };
当编译器看到第一个左大括号时,它期望看到一个数字(或其他数字表达式)。严格遵循 ISO C 和 C++ 标准的编译器会将其标记为格式不正确的代码。
请注意,将左大括号应用于标量类型对象的顶级初始化是合法的(但有些晦涩)。例如,以下是合法的: int i = { 0 }; // OK; 使用 0 初始化标量 i。 char *t = { "bar" }; // OK; 使用指针初始化标量 t,指向静态分配的数组。 ``` 另外请注意:如上例所示,此消息可以应用于指向 char 数组的指针;但不适用于数组本身
-
不完整数组后不可包含数据:允许在 C99 或 C++ 程序的结构体中包含不完整数组,但不允许在此数组后出现数据。例如:
c struct A { int x; int a[]; int b; };
当看到b
时发出此诊断。 -
对变量 ‘Symbol’(位置)的赋值增加了能力:对一个变量进行了赋值,这增加了能力。一个典型的能力增加是移除了
const
保护,如以下示例:c int *p; const int *q; p = q; // 错误 158
如果在赋值之外的情况下看到能力增加,或者变量不可用,将发出警告 605。请参阅该消息的描述以了解更多关于能力增加的信息。也可参阅第 13.8 节 C++ 信息性消息中的消息 1776 和 1778。 -
跟随类型的 enum 非标准:通常,不允许两个不同类型出现在同一类型说明中;这通常会导致错误 104。然而,一些编译器支持“带尺寸”的枚举,其中标量类型可以出现在 enum 关键字之前。例如:
c char enum color { red, green, blue };
当第二种类型是 enum 时,我们不会发出 104 而是发出错误 159。通过抑制此消息(使用-e159
),将支持这种构造。 -
序列 ‘( {’ 非标准,被视为 GNU 语句表达式的开始:在期望表达式(可能是子表达式)的上下文中,Lint 遇到了序列
( {'
。int n = ({ // Error 160 here int y = foo (); int z; if (y > 0) z = y; else z = - y; z; })// 现在 n 拥有 z 的最后一个值
此消息的主要目的是提醒用户该构造的非标准性质。典型的反应是抑制消息并继续。但是,有一些需要注意的问题。
计划只使用带有 GNU 扩展的 C 代码的程序员可以放心地禁用此诊断,但 C++ 用户应三思。这部分是由于 GCC 文档中提到的原因(请参阅“表达式中的语句和声明”部分),部分是因为当 G++ 实现初始化列表时(预计将在 2010 年版 ISO C++ 标准中出现的新核心语言特性),
( {'
的含义将发生变化。 -
在参数列表中重复使用参数 ‘Symbol’:函数参数的名称被重复使用。例如:
c void f(int n, int m, int n) {}
会引发此消息。给定函数的参数名称必须全部不同。
内部错误
2XX 内部错误:在 PC-lint/FlexeLint 系统中发现了某种不一致或矛盾。这可能是由于用户错误引起的,也可能不是。这种不一致应该报告给 Gimpel Software。
致命错误
这些类别的错误通常是致命的,并且通常无法抑制错误。然而,标记有星号(*)的错误可以被抑制,并且处理将继续。例如,使用 -e306
将允许重新处理模块。
-
堆栈溢出:在处理声明时发生了堆栈溢出。大约发现了 50 个嵌套的声明符。例如,如果一个
/
后跟 50 个连续的*
用于引入一个框式注释,并且省略了/
,则会产生此消息。 -
超出可用内存:主内存已耗尽。
-
字符串太长(尝试 +macros):单个
#define
定义或宏调用超过了内部限制(4096 个字符)。如诊断所示,可以通过选项来纠正问题。 -
损坏的对象文件,代码整数,符号=字符串:PC-lint/FlexeLint 对象文件显然已损坏。请删除对象模块并使用
-oo
选项重新创建它。消息中提供的特殊代码标识符号以及符号名称列表是用于技术支持诊断问题的工具。 -
无法打开模块 ‘文件名’:消息中提供了文件的名称。无法打开指定的模块进行读取。可能是因为您拼写错误了名称。
-
之前遇到的模块 ‘文件名’:文件名是模块的名称。已遇到指定的模块。这可能是用户的疏忽。
-
无法打开间接文件 ‘文件名’:文件名是间接文件的名称。无法打开指定的间接文件(以
.lnt
结尾)进行读取。 -
无法写入标准输出:
stdout
被发现等于NULL
。这是非常不寻常的情况。 -
#error…:遇到
#error
指令。省略号反映了原始行。通常在此点会终止处理。如果您设置了fce
(继续#error
)标志,则处理将继续。 -
声明太长:‘字符串…’:发现单个声明对于内部缓冲区来说太长了(大约 2000 个字符)。当尝试使用
-o...
选项写出声明时发生此情况。消息中的字符串给出了声明的前 30 个字符。通常这是由非常长的结构体引起的,其中的子结构体(如果有的话)未标记。首先识别出引起问题的声明。如果是结构体或联合体,请为任何未标记的子结构体或子联合体分配标记。使用typedef
也可以减小此声明的大小。 -
Lint 对象模块的版本 ID 过时或外来:整数:生成的 lint 对象模块是用先前或不同版本的 PC-lint/FlexeLint 生成的。请删除
.lob
文件并使用新版本的 PC-lint/FlexeLint 重新创建它。 -
文件过多:PC-lint/FlexeLint 能够处理的文件数量超出了内部限制。FlexeLint 用户可以重新编译系统以增加此限制。在
custom.h
中查找符号FSETLEN
。目前,文件数量限制为 4096 个。 -
之前使用过的 .lnt 文件:文件名:指定的间接文件已被之前遇到。如果这不是意外,您可以抑制此消息。
-
超过消息限制(见 -limit):超过了消息数量的最大限制。通常情况下没有限制,除非通过
-limit(n)
选项施加了限制。 -
写入文件 “文件名” 时出错:无法打开给定文件进行输出。
-
文件编码 ‘字符串’ 当前不支持:Lint 在文件开头检测到字节顺序标记,指示文件以给定格式编码。截至本书写时,唯一支持的格式是 ASCII 和 UTF-8(Lint 假设是 ASCII 编码)。
-
声明堆栈溢出:在处理声明符时(例如数组、指针、函数或引用修饰符)发生了堆栈溢出。
-
无法打开包含文件 ‘文件名’:消息中提供了文件名。无法打开指定的包含文件。目录搜索由选项
-i
、+fdi
和INCLUDE
环境变量控制。这是一个可抑制的致命消息。如果使用-e322
选项,将会触发错误消息 7,但处理将继续。 -
标记字符串太长:在尝试保存标记以供稍后使用时,固定大小的缓冲区已超出(由
M_TOKEN
的大小决定)。 -
符号过多整数:遇到的符号过多,超出了内部限制。
-
无法重新打开文件 ‘文件名’:在大量嵌套包含的情况下,需要在打开新文件之前关闭外部边缘文件。然后需要重新打开这些外部文件。尝试重新打开此类文件时发生错误。
-
字符串 ‘字符串 …’ 过长,超过整数个字符:字符串(消息中提供了前 40 个字符)超过了一些内部限制(消息中提供了限制值)。对此条件没有解决方法选项。FlexeLint 客户可以通过重新定义
M_STRING
(最大字符串)或M_NAME
(最大名称)来重新编译。在custom.h
中重新定义这些变量,可以通过适当的-dvar=value
选项来覆盖定义,前提是您的编译器支持该选项。 -
模块 ‘字符串’ 包含的文件1 与模块 ‘字符串’ 包含的文件2 之间的文件序列不同,导致名称为 ‘名称’ 的略过头文件出现在不同的头文件序列之后:当 #include 了一个之前已被指定为略过的头文件,并且已确定该头文件遵循与某些其他模块不同的头文件包含序列时,会发出此消息。消息中的第二个参数给出了其他模块的名称。为了避免将程序员埋没在一堆头文件名称中,我们试图确定两个模块分道扬镳的确切位置。另一个模块包含头文件的第一个区别发生在该模块包含消息中标识的
File1
文件,而当前模块试图包含标识为File2
的头文件时。每个Filei
都是字符串
(位置)形式的参数对,其中位置是 #include 的位置。例如: 模块 x.cpp: #include "alpha.h" #include "delta.h" #include "beta.h" #include "gamma.h" 模块 y.cpp: #include "alpha.h" #include "beta.h" #include "gamma.h"
当在模块 y.cpp 中包含
beta.h
时(如果beta.h
已被指定为略过),将会出现一个致命错误 328,表明模块 ‘x.cpp’ 的头文件序列与当前模块不同,因为前者在当前位置包含了delta.h
,而当前模块包含了beta.h
。由于尝试略过不遵循一致头文件序列的头文件是一种愚蠢的行为,因此必须将此消息视为致命错误。可以在 328 之后继续处理,以希望在其他模块中发现更多的不一致。可以使用
+fce
(继续错误)标志来实现此目的。
警告消息
-
符号 ‘Symbol’ 在位置之前未声明为 static:指定的符号在当前位置之前的某处声明过,但没有标记为
static
。根据 ANSI 标准,这种行为在技术上是违反规定的。某些编译器允许这种情况并默认为符号是静态的。 -
静态函数 ‘Symbol’(位置)未定义:命名符号在当前模块中被声明为
static
函数,并且被引用,但未在该模块中定义。 -
静态符号 ‘Symbol’ 具有不寻常的类型修饰符:某些类型修饰符(如
export
)与静态存储类不兼容。 -
结构体未在文件 ‘文件名’ 中完成:一个结构体(或联合体、枚举)在一个头文件中开始定义,但未在同一头文件中完成定义。
-
结构体未在文件 ‘文件名’ 内完成定义:在头文件中启动了一个结构体(或联合体或枚举)的定义,但未在同一头文件中完成。
-
#if 未在文件 ‘文件名’ 内关闭:在头文件中开始了一个 #if 结构,但未在该头文件中关闭。这是否是故意的?
-
注释未在文件 ‘文件名’ 内关闭:在头文件中开始了一个注释,但未在该头文件中结束。这是否是故意的?
-
标签 ‘Symbol’ 在使用位置与先前定义冲突:一个标签被指定为联合体、结构体或枚举,但在同一模块中的其他位置被重新定义为其他两者之一。例如
struct tag *p;
union tag *q;
会引发此消息。
-
与 switch 表达式的类型不匹配:case 中的表达式与 switch 表达式的类型不完全一致。例如,一个枚举类型与一个 int 类型匹配。
-
期望一个指针或数组:遇到一个 i[…] 形式的表达式,其中 i 是一个整型表达式。这在某些情况下可能是合法的,取决于下标操作数。例如,如果 i 是一个 int 而 a 是一个数组,那么 i[a] 是合法的但不常见。如果这是你的编码风格,可以抑制此消息。
-
size_t 类型与 fzl 和/或 fzu 不符,正在使用 ‘类型’:如果您之前尝试使用 +fzl、-fzl 或 -fzu 选项设置
sizeof
的类型,但后来的size_t
声明与该设置相矛盾,则会发出此警告。这通常意味着您试图使用适用于其他系统的头文件来检查自己系统的程序。如果是这样,建议您创建一个包含该外部系统头文件的目录,修改其中的size_t
类型,并使用该目录进行代码检查。 -
ptrdiff_t 类型与 fdl 选项不符,正在使用 ‘类型’:如果您之前尝试使用 fdl 选项设置指针差异的类型,但后来的
ptrdiff_t
声明与该设置相矛盾,则会发出此警告。请参阅错误消息 410 中的建议。 -
模糊的格式说明符 ‘%X’:使用
scanf
系列函数时,格式说明符%X
具有模糊性。在 Microsoft C 中,它表示%lx
,而在 ANSI C 中,它表示%x
。这种模糊的格式说明符不应出现在任何严肃的 C 程序中,应该使用上述之一替换。 -
在操作符 ‘字符串’ 的 [左/右] 参数中可能使用了空指针 ‘Symbol’:根据之前语句中的信息,似乎在一个不适合空指针的上下文中使用了一个空指针(其值为 0 的指针)。这些情况包括:一元
*
、指针递增(++)或递减(–)、将指针与数值相加,以及两个指针相减。对于二元操作符,使用left
或right
来指定哪个操作数为空指针。符号识别出可能为空指针的变量。 -
可能存在除以 0 的情况:对于除法操作符(/)或模数操作符(%)的第二个参数,可能为零。此信息源自先前的语句,包括赋值、初始化和测试。
-
通过操作符 ‘字符串’ 访问超出指针范围的数据(超出数据末尾 ‘整数’ 个单位):访问了一个超出指针范围的地址。
字符串
表示操作符,整数
参数表示指针可能超出数据范围的大小,以指向的对象大小为单位进行度量。例如:
int a[10];
a[10] = 0;
将导致一个溢出消息,包含短语 ‘超出数据末尾 1 个单位’。
- 通过操作符 ‘字符串’ 创建超出指针范围的地址(超出数据末尾 ‘整数’ 个单位):创建了一个超出指针范围的地址。有关
整数
和字符串
参数的描述,请参见消息 415。例如:
int a[10];
...
f(a + 11);
在这里,创建了一个非法指针值,并被 PC-lint/FlexeLint 标记为此类。需要注意的是,PC-lint/FlexeLint 不认为 a+10
是创建了一个超出范围的指针,这是因为 ANSI C 明确允许指向数组结尾处的地址。然而,通过 *(a+10)
或更常见的 a[10]
访问时,则被认为是错误的,并发出消息 415。
-
整型常量 ‘字符串’ 的精度超过了 long long int 的范围:默认情况下,最长的整数是 8 字节(参见 +fll 标志和 -sll# 选项)。发现一个整型常量甚至比这个范围还要大。例如:
0xFFFF0000FFFF0000F
。字符串
是出现问题的符号。 -
向函数 ‘Symbol’ 传递空指针,位置参考 ‘上下文’:正在向由
符号
标识的函数传递一个空指针。相关参数由上下文
给出。该函数可能是一个不应接收空指针的库函数,或是通过-function
选项指定的用户函数。 -
对于函数 ‘Symbol’ 的参数超出范围,参数 ‘整数’ 超过了参数 ‘整数’:当数据传输函数(如
memcpy
、strcpy
、fgets
等)的第一个参数指定的大小超过了缓冲区的大小时,将发出此消息。此消息也可能通过-function
选项用于用户函数。 -
对于函数 ‘Symbol’,数组访问超出范围,参数 ‘整数’ 超过了 ‘整数’ 参考:当库函数(如
fwrite
、memcmp
等)中出现尝试访问超过实际数据长度的数据时,将发出此消息。例如,如果fwrite
调用中指定的数据长度超过了实际数据的大小,将会引发此消息。函数由符号
指定,参数由参数编号标识。 -
警告——函数 ‘Symbol’ 被认为是危险的:默认情况下,此消息会针对内置函数
gets
进行发出。该函数被认为是危险的,因为无法确保提供的缓冲区(作为第一个参数)不会溢出。一个众所周知的计算机病毒(严格来说是蠕虫)就是基于这个缺陷创建的。用户可以通过-function
选项将其他函数指定为危险函数。 -
向函数 ‘Symbol’ 传递负值 ‘整数’,上下文参考 ‘上下文’:正在向一个只接受正值的函数传递一个看似为负的整数。消息包含函数名称(
符号
)、可疑值(整数
)和参数编号(上下文
)。该函数可能是一个设计为只接受正值的标准库函数(如malloc
或memcpy
的第三个参数),或是用户通过-function
或-sem
选项标识的函数。 -
创建内存泄漏时分配给变量 ‘Symbol’:向指针变量(由
符号
指定)分配了内存地址,而该指针先前似乎已经指向了尚未释放的分配对象。未释放的内存分配被视为内存泄漏。 -
对 ‘名称2’ 数据的不适当释放(名称1):此消息表示,尝试对某种类型的数据使用不适当的释放操作(如
free()
、delete
或delete[]
),由名称1
指定。【12,条目5】 -
在处理语义 ‘字符串’ 时遇到 ‘消息’ 错误,位于符号 ‘字符串’:当在处理语义选项(
-sem
)时遇到语法错误时,将发出此警告。消息
取决于错误类型。第一个字符串
代表正在处理的语义部分,第二个字符串
表示首次发现错误时扫描的符号。 -
调用函数 ‘Symbol’ 违反了语义 ‘字符串’:当违反了用户定义的语义(通过
-sem
定义)时,将发出此警告消息。字符串
是被违反的语义子部分。例如:
//lint -sem( f, 1n > 10 && 2n > 10 )
void f(int, int);
...
f(2, 20);
会产生如下消息:
调用函数 'f(int, int)' 违反了语义 '(1n>10)'
-
// 注释在反斜杠 \ 处终止: 一行注释以反斜杠转义序列结尾。这意味着下一行将被视为注释的一部分(标准符合的编译器会这样处理——并非所有编译器都这样处理,所以请注意)。最好在行尾使用除反斜杠之外的其他字符结束。例如在行尾加上一个句号。如果您确实希望下一行也是注释,那么下一行应以双斜杠(//)开始。
-
操作符 ‘字符串’ 中的下标为负数(整数): 将一个负整数添加到数组或指针(指向由
malloc
、new
操作符等分配的区域)上。对于其来源未知的指针,不会发出此消息,因为一般情况下负数下标是合法的。 -
保管指针 ‘Symbol’(位置)尚未释放或返回: 自动存储类的指针被分配了内存,但该内存既没有被释放也没有返回给调用者。这意味着出现了“内存泄漏”。如果指针唯一指向存储区,则被认为是保管指针。如果指针已被复制,则不再是保管指针。例如:
int *p = new int[20]; // p 是保管指针
int *q = p; // p 不再是保管指针
p = new int[20]; // p 再次成为保管指针
q = p + 0; // p 仍然是保管指针
- 字符 ‘@’ 被用于指定变量位置,不符合标准 C/C++: 许多嵌入式系统的编译器都有一种声明语法,用于指定变量的内存位置而不是初始值。例如:
int x @0x2000;
指定变量 x
实际上位于内存地址 0x2000
。此消息提醒该语法非标准(尽管相当常见)。如果您有意使用此语法,可以抑制该消息。
-
模板参数编号 ‘整数’ 缺少标识符: 某个模板对象参数(而不是类型参数)没有提供标识符。这是否是疏忽?
-
可疑的
malloc
参数: 检测到以下模式:
malloc(strlen(e+1))
其中 e
是某个表达式。这是可疑的,因为它与常用的模式非常接近:
malloc(strlen(e)+1)
如果您确实打算使用第一个模式,那么可以用以下等效表达式来避免触发此错误:
malloc(strlen(e)-1)
- 分配的区域不足以容纳指针: 分配的内存区域被赋给了一个指针,而该指针的范围超出了分配的区域。这通常只会在使用库的分配例程(如
malloc
和calloc
)时发生。例如:
int *p = malloc(1);
当分配的内存区域太小,不足以容纳指针时,会发出此消息。此消息也适用于用户声明的分配函数。例如,如果用户提供了以下语义:
-sem(ouralloc,@P==malloc(1n))
我们会报告相同的消息。请注意,有必要指定返回的区域是新分配的(类似于 malloc
)。
- 反斜杠与换行符之间的空白被忽略: 根据 C 和 C++ 标准,任何反斜杠后紧跟的换行符将导致这两个字符的删除。例如:
#define A \
34
定义 A
为 34。如果反斜杠与换行符之间有空白或制表符,根据严格的标准解释,您定义的 A
将包含一个反斜杠。但这个空白对肉眼是不可见的,因此可能导致混淆。更糟糕的是,一些编译器会悄悄地忽略这些空白,从而使程序变得不具可移植性。
- 整型常量 ‘字符串’ 的精度为 ‘整数’,请使用 +fll 启用 long long: 发现了一个整型常量,其精度过大,超过了 long 的范围,但可以适合 long long。然而,启用 long long 类型的 +fll 标志并未设置。
请检查您为 long(-sl#
)和 long long(-sll#
)指定的大小是否正确。如果您的编译器支持 long long,请打开 +fll 标志,否则请使用较小的常量。
- 在宏 ‘Symbol’ 的调用中出现明显的预处理指令: 调用了一个类似函数的宏,其参数跨多行,并包含预处理语句。这几乎可以肯定是由于缺少右括号引起的错误。
根据标准 C 的规则,预处理指令被吸收到宏参数中,但随后不会被执行。因此,一些编译器将显然的预处理指令视为指令,这种做法是逻辑的,但不可移植。因此,最好避免这种结构。
- 将结构体 ‘Symbol’ 传递给省略号: 在省略号标识的位置将一个结构体传递给函数。例如:
void g()
{
struct A { int a; } x;
void f(int, ...);
f(1, x);
...
}
这种情况很不寻常,因此值得指出,因为它很可能是无意的。在非 POD(Plain Old Data)结构体的情况下,情况会更严重,此时的行为被认为是未定义的。
- 变量 ‘Symbol’ 的最后一个赋值未使用: 为变量赋值后,未再使用该变量的值。此消息在返回语句处或在块结束时发出,当变量超出范围时。例如,考虑以下函数:
void f(int n)
{
int x = 0, y = 1;
if (n > 0)
{
int z;
z = x + y;
if (n > z) { x = 3; return; }
z = 12;
}
}
在此函数中,我们可以报告在遇到返回语句时,x
被赋值的值未被使用。我们还报告在 z
超出范围时,最近一次赋给 z
的值未被使用。有关更多信息,请参见 13.4 C Informational Messages
的 消息 838
和 2.3.6 Initialization-is-considered-a-Write flag (-fiw)
和 2.3.7 Initialization-by-Zero-is-considered-a-Write flag (-fiz)
。
- for 语句不规则:第二表达式中测试的变量 ‘Symbol’ 与第三表达式中修改的变量不匹配:
for
语句的结构存在可疑之处。根据对for
语句第三个表达式的检查,循环变量与第二个表达式中测试的变量不匹配。例如:
for (i = 0; i < 10; j++)
...
会引发此消息,因为第二个表达式中的 i
与第三个表达式中的 j
不匹配。
- for 语句不规则:第二表达式中未找到循环变量 ‘Symbol’:根据对
for
语句第三个表达式的检查,发现了一个循环变量(其名称在消息中给出),但该变量未出现在条件表达式(第二个表达式)中。例如:
for (p = a; *p; j++)
...
会引发此消息,因为第二个表达式中没有出现第三个表达式中的 j
。
- for 语句不规则:测试方向与递增方向不一致:检测到
for
语句存在一个方向问题。例如:
for (i = 0; i < 10; i--)
...
在这里,测试 i
是否小于 10 的条件与第三个表达式中的递减操作不一致。如果在第二个表达式中测试 i
是否大于某个值,而在第三个表达式中递增 i
,也会触发此消息。
- for 语句不规则:第三表达式中修改的变量 ‘Symbol’ 与第一个表达式中初始化的变量不匹配:
for
语句的结构存在可疑之处。根据对for
语句第三个表达式的检查,循环变量与第一个表达式中初始化的变量不匹配。例如:
for (ii = 0; i < 10; i++)
...
会引发此消息,因为第一个表达式中的 ii
与第三个表达式中的 i
不匹配。
- for 语句不规则:第三表达式中递增的指针 ‘Symbol’ 在第二表达式中被测试为 NULL:检测到以下类型的情况:
for (... ; p == NULL; p++)
...
通常情况下,不会测试递增或递减的循环变量是否为 NULL。这更可能是编程错误。
- 在 ‘位置’ 重复使用 for 循环变量 ‘Symbol’ 可能导致混乱:在一个
for
循环嵌套的另一个for
循环中使用了相同的循环变量。例如:
for (i = 0; i < 100; i++)
{
...
for (i = 0; i < n; i++) { ... }
}
会引发此消息,因为内外层循环使用了相同的循环变量 i
。
- 在 for 循环体内修改了循环变量 ‘Symbol’:在一个
for
循环中,循环体内也修改了循环变量。例如:
for (i = 0; i < 100; i++)
{
a[i++] = 0;
}
一般来说,最好将对 for
循环变量的修改限制在 for
语句中。如果无法做到,可以在 for
循环前加上适当的注释,例如:
/*lint -e{446} i 在 for 循环体内被修改 */
-
在 #include 指令中忽略了多余的空格:在包含文件 ‘文件名’ 的过程中,发现 #include 指令中包含了前导或尾随空格。虽然是合法的,ISO 标准允许编译器定义如何指定文件或标识头文件的外观,包括在
<
或引号"
之后或>
或引号"
之前出现的空白字符。由于文件名通常不包含前导或尾随空格,因此 Lint 会忽略(显然是)多余的字符,并像没有给出这些字符一样处理指令。使用 -efile 选项可使 Lint 保持 #include 指令中的空白字符不变。 -
可能访问指针越过 NUL 字符的位置 ‘整数’ 字节,操作符 ‘字符串’:访问 NUL 终止字符后的位置通常表示程序错误。例如:
char buf[20];
strcpy(buf, "a");
char c = buf[4]; // 合法但可疑。
虽然 buf
有 20 个字符,但在 strcpy
之后,程序员通常只对前两个字符感兴趣。
-
指针变量 ‘Symbol’ 先前已被释放:指针变量(在消息中指定)在先前的语句中已被释放或删除。
-
头文件 ‘文件名’ 被重复包含但没有标准的包含保护:在当前模块中,消息中提到的文件已被包含过。此外,确定该头文件没有标准的包含保护。标准的包含保护的形式是:
#ifndef Name
#define Name
...
#endif
在这个序列之前和之后都不应该有其他内容,只有注释。#ifndef 和 #define Name 之间也只有注释。
该警告可能伴随着 537(重复包含头文件)的出现。通常会抑制 537 消息,因为如果您正在使用包含保护, 它不是一个有用的消息。然而,451 消息应保留,以检查包含保护的一致性。
- 标头文件 ‘FileName’ 被重复包含但没有标准的包含保护 – 消息中提到的文件已经在当前模块中被包含过。此外,已确定该头文件没有标准的包含保护。标准的包含保护形式如下:
#ifndef Name
#define Name
...
#endif
在此序列之前和之后只能有注释,#ifndef 和 #define Name 之间只能有注释。
此警告也可能伴随有 537(重复包含头文件)警告。消息 537 通常会被抑制,因为如果你使用了包含保护, 这条消息并不太有用。然而,消息 451 应该保持开启,以检查包含保护的一致性。
另见第13.5节 C 选择性注释 967。
-
typedef 符号 ‘Symbol’ 重新声明(类型差异)与位置冲突:一个
typedef
符号被声明为不同的类型。这可能是合法的,特别是在多个模块的情况下,但这不是一种良好的编程实践。这会影响程序的可读性。 -
函数 ‘Symbol’ 之前被标记为纯函数,‘字符串’ 违规:通过语义选项,指定了名为
符号
的函数是纯函数(没有非本地副作用;请参阅第4章的纯语义)。然而,检测到了一个“杂质”。这些“杂质”包括通过函数指针调用函数、访问易变变量、修改静态变量或调用 PC-lint/FlexeLint 无法验证其纯度的函数。字符串
描述了应用的原因,名称
显示了相关的变量或函数。
尽管报告了不一致,但该函数仍将被视为纯函数。
- 一个线程互斥锁已被锁定但未解锁:函数中到达了一个返回点,此时先前设置的互斥锁尚未解锁。例如:
//lint -sem(lock, thread_lock)
void f(int x)
{
lock();
if (x < 0) return; // 警告 454
...
}
- 一个未锁定的线程互斥锁被解锁:在未被前置锁定的情况下,调用了一个
unlock()
函数。假定每个lock()
函数调用必须与一个unlock()
函数平衡,不多不少。例如:
//lint -sem(lock, thread_lock)
//lint -sem(unlock, thread_unlock)
void f(bool x)
{
lock();
/* 做某些事情 */;
unlock();
/* 做其他事情 */
unlock(); // 警告 455
}
- 两条执行路径在具有不同互斥锁状态下合并:此消息的目的是确保每个锁都在同一段代码中以相同的顺序被解锁。
执行路径可能在 if
语句、switch
语句或 while
、for
和 do
语句的开头、标签(goto
的目标)等地方合并。在所有这些情况下,我们会检查互斥锁状态是否一致。例如:
//lint -sem(lock, thread_lock)
void f(bool x)
{
if (x) lock();
// 此处发出警告 456
...
}
有人可能会争辩说,如果在相同的 bool x
控制下执行一个 unlock()
调用,那么一切都会很好。如果这是您的编码风格,您可以关闭此消息。但互斥锁定错误的编程后果非常严重,因此建议采取特别强有力的措施来确保代码正确性。我们建议,例如:
//lint -sem(lock, thread_lock)
//lint -sem(unlock, thread_unlock)
void f(bool x)
{
if (x)
{
lock();
/* 做某些事情 */;
unlock();
}
else
{
/* 做其他事情 */
}
}
如果正在执行的“某些事情”足够复杂,可以将其做成一个函数。
-
线程 ‘符号1’ 中的函数 ‘符号2’ 对变量 ‘符号3’ 进行了非保护的写访问,而该变量也被线程 ‘符号5’ 中的函数 ‘符号4’ 使用:一个变量(
符号3
)被线程符号2
的函数符号1
在没有任何已识别的互斥锁保护下修改。同时,该变量还被另一个线程符号5
中的函数符号4
访问。后者的访问可能也没有被保护。如果未受保护,将发出第二条消息,其中符号1
和符号4
的角色互换。 -
线程 ‘符号2’ 中的函数 ‘符号1’ 对变量 ‘符号3’ 进行了非保护的读取访问,而该变量被线程 ‘符号5’ 中的函数 ‘符号4’ 修改:此消息激活的条件与457类似,只不过该消息只会在变量
符号3
被函数符号1
读取时激活,而函数符号1
并未通过互斥锁保护。 -
函数 ‘Symbol’ 的地址被获取,但对变量 ‘Symbol’ 进行了非保护的访问:此消息仅在程序中出现多个线程时才会激活(有关条件,请参见第8章“多线程支持”)。如果获取了一个函数的地址,则假定无法静态地确定该函数可能被调用的所有位置,因此假设任何线程都可以调用此函数,因此需要对该函数可能访问的每个静态变量进行保护。
对于此类消息有几种处理方法。如果确实有多个线程可以访问此函数,请在函数中放置一个互斥锁。如果已经有了互斥锁但未被识别,则为该函数设置 thread_protected
语义。如果只有一个线程访问此函数,或者访问被保证是无害的,那么在确保此条件已在代码中有注释后,可以为该函数使用相同的 thread_protected
语义。
- 线程 ‘Symbol’ 对线程不安全的函数 ‘Symbol’ 进行了非保护的调用,该函数还被线程 ‘Symbol’ 调用:消息中的第二个符号表示通过
-sem
选项被标记为线程不安全的函数。它在未受保护的线程区域内被调用,而消息中的第一个符号表示的线程也在访问该函数。
如果要由多个线程使用线程不安全的函数,则需要通过互斥锁进行保护。
-
线程 ‘Symbol’ 对函数 ‘Symbol’ 进行了非保护的调用,而该函数属于组 ‘名称’,同时线程 ‘Symbol’ 也调用了相同组的函数 ‘Symbol’:此消息与警告 460 类似,不同之处在于消息中的线程(由第一个符号标识)正在调用一个函数(第二个符号),该函数被推断为属于线程不安全的组(通过选项确定)。在此情况下,另一个线程也在调用一个属于相同组的函数(第三个符号标识该组)。更多信息请参见第8章“多线程支持”中的线程不安全函数组及其选项。
-
线程 ‘Symbol’ 调用的函数 ‘Symbol’ 与 ‘字符串’ 语义不一致:消息中的第一个符号标识一个线程,第二个符号标识该线程直接或间接调用的函数。
字符串
参数指定了一个与函数相关联的语义,格式如下:
thread_not
thread_not(list)
thread_only(list)
如果给出了第二种格式,则意味着线程出现在列表中。如果给出了第三种格式,则意味着线程不在列表中。
- 缓冲区参数将被复制到自身:当遇到一个函数参数表达式,以至于将尝试将其内容复制到自身时,将发出此消息。例如:
sprintf(s, "%s", s);
-
预期的类型为有符号类型:对无符号类型应用了一元减号操作符。结果值为正的无符号数量,可能与预期不符。
-
预期的类型为无符号类型:一元
~
是一个位操作符,通常更适合应用于无符号数量而非有符号数量。 -
布尔类型的参数用于关系操作:通常情况下,关系操作符不会有布尔类型的参数。例如,表达式
a < b < c
虽然在技术上是合法的,但并不会产生与数学表达式相同的结果。 -
不寻常的移位操作(字符串):移位的数量或被移位的数量通过不寻常的方式得出,例如通过位运算操作符、否定或未加括号的表达式。如果移位值是一个复合表达式,且未加括号,则应加括号。
-
逗号运算符的左边参数多余:逗号运算符的左边参数的顶层运算符没有副作用,因此是多余的。
-
布尔常量值:在需要布尔值的上下文中(例如作为
&&
或||
的参数,或在if()
或while()
语句中)发现了一个常量,因此每次评估时结果都相同。 -
大小不兼容:将指针类型转换为整型量时,根据给定或隐含的信息,它可能不适合。例如,将一个指针转换为无符号整型,而根据选项提供的信息,指针的大小大于整型。
-
使用
extern
关键字定义函数:函数定义时使用了extern
存储类。extern
通常用于声明而非定义。最好情况下,extern
是多余的。最坏情况下,可能会导致编译器错误。 -
使用
extern
关键字定义数据对象:数据对象定义时使用了extern
存储类。这在 ANSI 中是技术上合法的,但目前不推荐此做法,因为它可能容易导致编译器错误。 -
大小不兼容:尝试将整型类型转换为指针类型时,整型量的大小过大,无法适应指针。例如,如果将
long
类型转换为指针,并且根据选项显示long
大于指针,则会报告此警告。 -
符号 ‘Symbol’ 之前在位置被用作静态:给定的符号名称是函数名,在其他模块中被声明为静态(消息中提供了声明的位置)。在一个模块中将名称用作静态,而在另一个模块中用作外部是合法的,但值得怀疑。
-
布尔值的非寻常使用:一个布尔类型的参数被用作算术运算符(
+
、-
、/
、*
、%
)或位运算符(|
、&
、^
)的参数。这通常是由于程序员意外导致的,例如:
if (flags & 4 == 0)
由于 ==
比 &
的优先级更高,因此会先进行 ==
运算,这往往会让程序员感到困惑。
-
符号 ‘Symbol’ 的参数数量与位置处的参数数量冲突(整数 vs. 整数):在函数调用中,实际提供的参数数量与函数定义中的形式参数数量,或在另一个函数调用中的实际参数数量不一致。请参阅
+fva
选项以选择性地抑制此消息。 -
符号 ‘Symbol’ 的参数类型与位置处的参数类型冲突(参数号 整数 – 类型差异):在函数调用中,实际参数的类型与函数定义中相应形式参数的类型,或另一次对同一函数的调用中的实际参数类型,或函数原型中指定的参数类型不一致。调用时未提供原型。请参阅
-ean
、-eau
、-eas
和-eai
选项,以选择性地抑制某些类型差异。如果冲突涉及char
或short
类型,您可能需要考虑使用+fxc
或+fxs
选项。 -
defined
不是 K&R 标准:使用了defined
函数(它不是 K&R 结构),而且设置了 K&R 预处理器标志(+fkp
)。要么不设置标志,要么不使用defined
。 -
预期为 ‘(’:
sizeof
类型不严格符合 C 标准。sizeof(type)
或sizeof expression
是允许的。 -
大小不兼容:尝试将指针类型转换为大小不相等的指针类型。例如,在 P 模型中,函数指针需要 4 字节,而数据指针只需要 2 字节。可以通过先将指针转换为整型量(
int
或long
),然后再转换为指针类型来规避此错误消息。 -
最高运算符或函数没有副作用:
for
语句的第一个表达式应为以下特权运算符之一:赋值、递增、递减或调用一个不纯函数或修改其参数的函数。 -
最高运算符或函数没有副作用:
for
语句的第三个表达式应为以下特权运算符之一:赋值、递增、递减或调用一个不纯函数或修改其参数的函数。 -
最高运算符或函数没有副作用:如果一个语句只包含一个表达式,那么它应该是以下特权运算符之一:赋值、递增、递减或调用一个不纯函数或修改其参数的函数。例如,如果
*
是内置运算符,语句*p++;
会引发此消息,而p++;
则不会。因为最高的运算符是*
,它没有副作用。
纯函数和不纯函数以及具有副作用的函数调用的定义在第4章“语义”中的纯函数部分有详细说明。
-
精度丢失(上下文)(类型到类型):在从浮点数转换为整型量时可能会丢失小数部分。使用强制类型转换可以抑制此消息。
-
从位置的负缩进:当前行的缩进量小于给定行(消息中指定的位置)的缩进量。给定行是控制结构的起始语句,并且其范围内的语句和其他控制结构或括号预计不会有更少的缩进。如果程序中的制表符与空格的长度不同,应使用
-t
选项。 -
** ‘Symbol’(位置)未定义**:引用了指定的外部符号,但没有定义它,并且它没有出现在任何库头文件中,也没有出现在库模块中。对于单元检查(
-u
选项),此消息被抑制。请注意,声明(即使包含原型信息)不是定义。请参阅本章开头的术语表。如果符号是库符号,请确保它在包含的头文件中被声明。还要确保头文件被 PC-lint/FlexeLint 视为库头文件。或者,符号可能是在库模块中声明的。 -
在符号处的代码不可达:程序的一部分无法到达。
-
符号 ‘Symbol’(位置)未引用:在声明了指定的静态变量或静态函数后,未在模块中引用该符号。
-
符号 ‘Symbol’(位置)未被后续引用:在函数中声明了指定的变量,但未引用它。
-
符号 ‘Symbol’ 未初始化:在使用一个自动变量之前,它未被初始化。
-
位域的字段大小对于 ‘Symbol’ 来说太大:结构体位域指定的大小超过了
int
的大小。 -
函数 ‘Symbol’ 的返回模式与位置不一致:函数的声明(或定义)表明其返回模式与先前语句不同。(函数的返回模式与函数是否返回值有关。)返回模式通过检查声明中是否返回
void
来确定,或者通过检查是否给出了明确的类型来确定。有关进一步说明,请参阅fdr
标志。还请参阅fvr
和fvo
标志。 -
函数 ‘Symbol’ 应该(或不应该)返回一个值(参见位置):函数中的返回语句(或缺少的返回语句)表明其返回模式与位置处的先前语句不同(函数的返回模式与函数是否返回值有关)。
-
忽略了函数 ‘Symbol’ 的返回值(与位置比较):调用一个返回值的函数仅为了副作用,例如在单独的语句中或作为逗号运算符的左侧。尝试使用
(void) function();
来调用函数并忽略其返回值。还请参阅fvr
、fvo
和fdr
标志。 -
重复包含文件 ‘文件名’:请求包含的文件已在当前模块中包含过。即使给出了此消息,该文件也会正常处理。如果您通常会重复包含文件,请简单地抑制此消息。
-
大小过大:数组的大小等于或超过 64K 字节。
-
不期望从位置的正缩进:当前行的缩进量大于一个与之不相关的语句的缩进量。例如:
if (n > 0)
x = 3;
y = 4;
这会导致 y = 4;
发出此警告。消息中引用的位置将是 if
语句的位置。
-
大小过大:字符串初始化程序所需的空间超过了分配的空间。
-
大小过大:使用
\xddd
或\xhhh
指定的字符常量的大小等于或超过了 2 的 b 次方,其中 b 是一个字节的位数(由-sb
选项确定)。默认情况下为-sb8
。 -
位域的大小过大:尝试将一个值分配到一个看起来太小的位域中。要么是分配的值比目标位域大,要么是一个数值过大。您可以通过将值强制转换为通用的
unsigned
类型来抑制该错误。
如果位域的基类型是 int
,可能会意外收到此消息。例如:
struct { int b : 1 } s;
s.b = 1; // 警告 - 需要 0 或 -1
在这种情况下,解决方法是将 b
声明为 unsigned
而不是 int
。
-
#endif 或 #else 之后没有换行符:预处理指令
#endif
后应有一个换行符。一些编译器允许在#endif
后跟随注释。如果您遵循此约定,则可以简单地关闭此错误消息。 -
可疑的 & 运算符使用:试图获取数组名的地址。在某个时候,这种表达式在官方上是非法的(K&R C),它没有得到一致的实现,因此是可疑的。然而,在 ANSI C 中,该表达式是合法的,并指定为指向数组的指针。例如,给定:
int a[10];
int (*p)[10];
a
和 &a
作为指针都代表相同的位模式,但是 a
是一个指向 int
的指针,而 &a
是一个指向 int[10]
的指针。只有 &a
可以分配给 p
而不会引起问题。如果您以这种方式使用 &
运算符,建议您禁用此消息。
-
可疑的 & 运算符使用:试图获取函数名的地址。由于函数名本身会被提升为地址,因此使用
&
是多余的,可能是错误的。 -
符号 ‘Symbol’ 的重新定义与位置冲突:指定的符号之前已经通过
#define
被定义为其他值。 -
期望
else
:发现了if(e);
形式的结构,但未跟随else
。这几乎可以肯定是一个不需要的分号,因为它会使if
语句没有任何效果。 -
可疑的类型转换:从指针到某种枚举类型或从枚举类型到指针的类型转换。这可能是一个错误。检查您的代码,如果这不是错误,请先将项目转换为中间形式(如
int
或long
),然后再进行最终转换。 -
符号 ‘Symbol’(位置)未访问:一个局部变量未被访问。这意味着变量的值从未被使用过。可能变量被赋值但未被使用。注意,变量的值在自增或自减时不被视为访问,除非自增/自减出现在一个更大的表达式中,该表达式使用了结果值。同样适用于
var += expression
形式的结构。如果获取了一个变量的地址,则认为它的值已被访问。数组、结构或联合被视为已访问,如果它们的任何部分被访问。 -
符号 ‘Symbol’(位置)未访问:在模块级别声明的静态变量未被访问,尽管该变量被引用过。请参见消息 550 的说明,了解“访问”的描述。
-
符号 ‘Symbol’(位置)未访问:一个外部变量未被访问,尽管该变量被引用过。请参见消息 550 的说明,了解“访问”的描述。
-
未定义的预处理器变量 ‘名称’,假定为 0:在预处理器条件
#if
或#elif
中使用了未在#define
语句中预定义的变量。按惯例,预处理器表达式中的所有变量都应预先定义。该变量的值被假定为 0。 -
#elif 不是 K&R 标准:使用了
#elif
指令,并且设置了 K&R 预处理器标志(+fkp
)。要么不设置该标志,要么不使用#elif
。 -
缩进的 #:一个预处理器指令在行内缩进,并且设置了 K&R 预处理器标志(
+fkp
)。要么不设置该标志,要么不对#
进行缩进。 -
未识别的格式:
printf
、fprintf
、sprintf
、scanf
、fscanf
或sscanf
提供的格式字符串未被识别。它既不是标准格式,也不是用户定义的格式(请参阅printf_code
和scanf_code
)。 -
格式参数不足(缺少整数个):提供给
printf
、sprintf
、fprintf
、scanf
、fscanf
或sscanf
的参数数量与根据格式字符串分析预期的数量不一致。 -
参数号的大小与格式不一致:提供给
printf
、sprintf
或fprintf
的参数与根据格式字符串分析得出的预期不一致。参数计数从 1 开始,包括文件、字符串和格式规范。例如:
sprintf(buffer, "%f", 371)
会显示第3个参数的错误,因为常量 371
不是浮点数。
- 参数号应为指针:提供给
scanf
或printf
系列函数的参数应为指针。对于scanf
系列函数,所有与格式说明符对应的参数都应为指向将被修改的区域的指针(接收扫描结果)。对于printf
系列函数,与%s
或%n
对应的参数也需要是指针。
参数计数从 1 开始,包括文件、字符串和格式说明。例如:
scanf("%f", 3.5)
会生成消息,指出第2个参数应该是指针。
- (参数号 整数)间接对象与格式不符:提供给
scanf
、sscanf
或fscanf
的参数是一个指向与根据格式字符串分析预期的不一致的对象的指针。参数计数从 1 开始,包括文件、字符串和格式说明。例如,如果n
被声明为int
类型,则:
scanf("%c", &n)
会针对第2个参数发出此消息。
-
省略号(…)假定存在:在函数原型中,逗号后紧跟一个右括号。一些编译器认为这是等同于省略号(三个点),而 PC-lint/FlexeLint 就会做此假设。如果您的编译器不接受省略号但做出此假设,则应抑制此消息。
-
标签 ‘Symbol’(位置)未被引用:消息中引用的位置出现了一个标签,但没有语句引用该标签。
-
变量 ‘Symbol’ 依赖于评估顺序:在一个表达式中,命名变量被修改并访问,其结果取决于是从左到右还是从右到左进行评估。例如:
n + n++
因为无法保证第一次访问 n
发生在 n
递增之前。其他更典型的例子可以在手册中找到。对于易变变量,也会检查在表达式中重复使用。
- 标签 ‘Symbol’ 之前未见,假定为文件级作用域:命名标签出现在原型或内部块中,未在外部(文件级别)作用域中出现。ANSI 标准对于此标签如何与任何其他标签链接持怀疑态度。对于大多数编译器,这不是一个错误,可以安全地抑制此消息。另一方面,要严格符合 ANSI C 标准,您可以在程序的更早部分放置一个小的声明。例如:
struct name;
足以为 name
在符号表的适当级别保留一个位置。
-
不一致或冗余的格式字符 ‘字符’:此消息适用于
printf/scanf
系列函数的格式说明符。格式说明符中发现的指定字符Char
与之前发现的字符不一致或冗余。例如,格式中包含%ls
将引发此错误,并指示字符 ‘s’。这是因为长度修饰符设计用于整数或浮点转换,而对于字符串转换则没有意义。这些字符通常被编译器忽略。 -
在字符 ‘字符’ 之前应有一个数字字段:此消息适用于
printf/scanf
系列函数的格式说明符。在扫描格式时的某个点期望出现一个数字字段或星号。例如:
%-d
请求在格式字段中左对齐一个十进制整数。但由于没有给定字段宽度,因此请求是无效的。
- 非负数量永远不会小于零:如果
u
是无符号数量或被认为永远不小于 0 的数量,则形式为:
u >= 0
0 <= u
u < 0
0 > u
的比较是可疑的。参见消息 775。
-
信息丢失(上下文)(整数位到整数位):将一个常量分配(或隐式分配,参见上下文)给一个整数变量,而该变量不足以容纳该常量。例如,将一个位需求大的十六进制常量放入一个
int
类型的变量中。给出的位数不包括符号位。 -
符号丢失(上下文)(类型到类型):将一个负常量分配(或隐式分配,参见上下文)给无符号量。将常量转换为无符号将消除诊断问题,但这是否是您想要的?如果要将所有位设置为 1,请记住
~0
表示所有位为 1,比-1
更具可移植性。 -
可疑的类型转换:通常,此警告适用于以下形式的转换:
(unsigned) ch
其中 ch
被声明为 char
类型,并且 char
是有符号的。尽管类型转换可能看起来可以防止 ch
的符号扩展,但实际上并没有。按照 C 的正常提升规则,ch
首先转换为 int
,这会扩展符号,然后才将其转换为无符号类型。要抑制符号扩展,可以使用:
(unsigned char) ch
否则,如果需要符号扩展并希望仅在此情况下抑制警告,可以使用:
(unsigned) (int) ch
虽然这些例子是针对 char
的类型转换给出的,但它们也适用于对小于转换类型的有符号量的类型转换。包括有符号位字段(在新标准中是可能的)、涉及 char
的表达式、涉及 short
的表达式(当该类型小于 int
时)或直接将 int
转换为无符号 long
(如果 int
小于 long
)。此消息不会针对常量或涉及位操作的表达式发出。
- 过大的移位值(精度 整数 右移 整数):正在右移的数量,其精度等于或小于移位值。例如:
ch >> 10
如果 ch
的类型为 char
,而 char
的宽度小于 10 位(通常情况下),则会发出此消息。要抑制该消息,可以将移位数量转换为至少与移位值长度相同的类型。
- 带有除法运算的有符号和无符号混合:除法
/
或取余%
的一个操作数是有符号的,另一个是无符号的;此外,有符号的数量可能是负数。例如:
u / n
其中 u
是无符号的,n
是有符号的将引发此消息,而:
u / 4
则不会,尽管 4
名义上是 int
。无论如何,混合使用无符号量和有符号量都不是一个好主意(还会发出 737
消息),但在除法情况下,负值可能会造成混乱。例如,看似无害的:
n = n / u
如果 n
是 -2,u
是 2,不会将 -1 分配给 n
,而是分配一个非常大的值。
为了解决此问题,如果确定整数永远不会小于零,可以将其转换为无符号;或者如果确定无符号永远不会超过最大整数,则可以将无符号转换为整数。
- 带有关系运算的有符号和无符号混合:四个关系运算符是:
> >= < <=
其中一个操作数是有符号的,另一个是无符号的;此外,有符号的数量可能是负数。例如:
if (u > n) ...
其中 u
是无符号的,n
是有符号的将引发此消息,而:
if (u > 12) ...
不会引发消息(尽管 12
官方上是 int
,但显然它不是负数)。无论如何,混合使用无符号量和有符号量都不是一个好主意(还会发出 737
消息),但在四个关系运算符的情况下,负值可能会产生模糊的结果。例如,如果条件:
if (n < 0) ...
为真,那么类似的:
u = 0;
if (n < u) ...
为假,因为提升为无符号后 n
会变得非常大。
为了解决此问题,如果确定整数永远不会小于零,可以将其转换为无符号;或者如果确定无符号永远不会超过最大整数,则可以将无符号转换为整数。
575. 枚举常量超出整数范围:对于许多编译器,枚举常量的值受到限制,只能在signed int
或unsigned int
范围内。
577. 混合内存模型(选项 ‘String’):所指示的选项在处理部分或全部其他模块之后请求更改内存模型。内存模型选项应在处理任何模块之前指定。此错误最常见的原因是在指定标准库之后才指定内存模型。如果通过 LINT 环境变量指定标准库文件,可能很容易犯这个错误。
578. 符号 ‘Symbol’ 的声明隐藏了符号 ‘Symbol’ (位置):局部符号与全局符号(或可能是另一个局部符号)同名。这可能是危险的。这是故意为之的吗?通常最好重命名局部符号。
579. 在省略号之前的参数类型无效:当使用省略号时,省略号前的类型不应为会进行默认提升的类型,如 char
、short
或 float
。原因是许多编译器的可变参数方案(使用 stdarg.h
)会因此而失败。
580. 函数 ‘Symbol’ 的重新声明(隐藏位置)导致原型丢失:在代码块中声明函数,隐藏了外部作用域中的声明,使得内部声明没有原型,而外部声明有原型。一个常见的误解是,结果声明是两者的组合,但只有当声明在同一作用域内时才会如此,而在嵌套作用域中则不会。如果不关心原型,可以抑制此消息。你仍然会收到其他类型差异的警告。
581. 选项 ‘String’ 已被淘汰,不应再使用:每当遇到一个选项并且该选项显得弊大于利时,都会发出此消息。这里的 ‘String’ 是所指的选项。
582. esym(或 emacro)名称 ‘String’ 不应包含 ‘(’:提供给 esym
的名称不应包含 ‘(’。例如,要在调用 f(int)
时抑制消息 534,请使用选项 -esym(534,f)
,即使 f
被重载。
583. 将类型 ‘Type’ 与 EOF 进行比较:当某种形式的字符与 EOF 宏进行比较时会发出此消息。EOF 通常被定义为 -1。例如:
while( (ch = getchar()) != EOF ) ...
如果 ch
被定义为 int
,那么一切正常。然而,如果 ch
被定义为某种形式的 char
,则可能会出现问题。如果 ch
是 unsigned char
,它将永远无法等于 EOF;如果 ch
是 signed char
,则可能会由于某个数据字符碰巧全为1而导致过早终止。
请注意,getchar
返回的是 int
。它返回 int
而不是 char
的原因是它必须能够返回 257 个不同的值(假设是 8 位字符,256 个不同的字符加上 EOF)。一旦这个值被分配给 char
,那么只可能表示 256 个值,信息就明显丢失了。
584. 检测到三字母序列 (??Char):每当检测到三字母序列且三字母处理已关闭(使用 -ftg
选项)时,将发出此消息。如果这不是在字符串(或字符)常量中,则忽略三字母序列。如果你的编译器不处理三字母序列并且你希望 linter 与编译相匹配,那么此功能是有用的。在字符串之外,我们发出警告,但会翻译该序列,因为在其原始状态下它无法在语法上成立。
585. 序列 (??Char) 不是有效的三字母序列:每当在字符串(或字符)常量中看到两个 ‘?’ 字符,但它们后面没有一个字符使该三字母序列有效时,将发出此警告。程序员是打算使用三字母序列但只是犯了个错误吗?即使没有打算使用三字母序列,阅读代码的人也很容易将其误认为是三字母序列。此外,无法保证将来这个无效的三字母序列不会变成有效的,并改变字符串的含义。为防止这种情况,可以在 ‘?’ 字符之间放置反斜杠。或者,可以使用字符串常量的连接。例如:
pattern = "(???) ???-????"; // 警告 585
pattern = "(?\?\?) ?\?\?-?\?\?\?"; // 无警告
#define Q "?"
pattern = "(" Q Q Q ") " Q Q Q "-" Q Q Q Q // 无警告
586. 字符串 ‘Name’ 已被弃用。字符串:Name
由于某种使用弃用选项的原因被弃用。请参阅第 2.5.7 节 -deprecate。第一个字符串是允许的弃用类别之一。最后一个字符串是弃用选项的一部分,应该解释该功能为何被弃用。
587. 断言 ‘String’ 可以被预先确定,并且总是评估为 ‘String’:由第一个字符串标识的断言(大于、大于或等于、小于、小于或等于、等于或不等于)不可能与第二个字符串参数指定的值不同。例如:
unsigned u; ...
if( (u & 0x10) == 0x11 ) ...
将发出一条消息,指出 ‘==’ 总是评估为 ‘False’。
588. 断言 ‘String’ 总是评估为 ‘String’,除非发生溢出:由第一个字符串标识的断言不可能与第二个字符串参数指定的值不同,除非发生溢出。例如:
unsigned u; ...
if( (u + 2) != 1 ) ...
将发出消息,指出 ‘!=’ 总是评估为 ‘True’。参见消息 587。
589. 断言 ‘String’ 总是评估为 ‘String’,假设标准除法语义:由第一个字符串参数标识的断言在假设标准有符号整数除法语义的情况下不可能与第二个字符串参数指定的值不同。例如:
int n; ...
if( n % 2 == 2 ) ...
将发出消息,指出 ‘==’ 总是评估为 ‘False’。
标准整数除法语义指的是向零截断,例如 -1/4 的商为 0,余数为 -1,而不是商为 -1,余数为 3。尽管当前 C 标准[4]支持这种做法,但当前 C++ 标准[10]将涉及负数的整数除法视为“实现定义”。
590. 断言 ‘String’ 总是评估为 ‘String’,假设标准移位语义:由第一个字符串参数标识的断言在假设标准有符号整数移位语义的情况下不可能与第二个字符串参数指定的值不同。例如:
int n; ...
if( (5 << n) < 0 ) ...
将发出消息,指出“<”总是评估为“False”。虽然如果你向左移位 5 位(或 31 位),在许多情况下(可能是大多数情况下)你会得到一个负数,但这并不一定保证。根据 C 标准[4],如果类型无法容纳 5*(2**n),则向左移位正整数(此处为 5)是无效的。
591. 变量 ‘Symbol’ 依赖于评估顺序;它通过函数 ‘Symbol’ 进行了使用/修改,调用顺序为:String:所标识的变量(第一个符号)参与了一个表达式,该表达式包含了一个使用或修改该变量的函数调用(第二个符号)。此外,两者的评估顺序无法确定。例如:
extern int n;
void f() { n++; }
int g() { f(); return 1; }
int h() { return n + g(); } // 警告 591
在第二次通过时,上述代码将发出以下警告:
Warning 591: 变量 'n' 依赖于评估顺序;
它通过函数 'g(void)' 进行了修改,调用顺序为:g() => f()
如果首先调用 g()
,然后再对 n
进行相加,结果将不同于先评估 n
,然后再进行函数调用。程序员通常应重写这些表达式,使编译器按照预期的顺序执行。例如,如果程序员希望在调用 g()
之前使用 n
,可以将 h()
改为如下形式:
int h() { int k = n; return k + g(); }
该分析需要两次处理;第一次处理构建必要的调用树。
592. 非字面量格式说明符未带参数使用:printf/scanf
风格的函数接收到一个非字面量格式说明符,但没有后续的参数。例如:
char msg[100];
...
printf( msg );
这很容易被重写为相对安全的形式:
char msg[100];
...
printf( "%s", msg );
问题在于 msg
可能包含隐藏的格式代码。如果 msg
是从用户输入读取的,那么在第一个例子中,天真的用户可能会引发故障或崩溃,而恶意用户则可能利用此漏洞破坏系统安全。由于不安全的形式可以轻松转换为安全形式,因此应始终使用后者。
593. 可能未释放或未返回的托管指针 ‘Symbol’ (Location):这是消息 429 的“可能”版本。一个具有自动存储类的指针被分配了存储空间,并且并非所有通往返回语句或函数结束的路径都包含 free
或返回该指针。因此,有潜在的内存泄漏。例如:
void f( int n ) {
int *p = new int;
if( n ) delete p;
} // 消息 593
在此示例中,进行了一次分配,如果 n
为 0,则不会调用 delete
。
请参阅消息 429,了解有关“托管”及管理指针变量何时保持分配所有权的方法。
601. 预期符号 ‘Symbol’ 的类型为 ‘int’,但推断为 ‘int’:某个声明没有明确的类型,因此假定为 int
。这是一个错误吗?这通常发生在预期的逗号被误写为分号的情况下。例如:
double radius, diameter;
如果程序员错误地输入为:
double radius;
diameter;
将发出此消息。
602. 注释嵌套在注释中:在注释中发现了 /*
序列。这是故意的吗?还是因为忘记了结束注释符号?如果希望 PC-lint/FlexeLint 识别嵌套注释,应使用 +fnc
选项设置嵌套注释标志,那么将不会发出此警告。如果你习惯使用以下形式:
/*
/* */
可以使用 -e602
来抑制此消息。
603. 符号 ‘Symbol’ (位置) 未初始化:正在将名为 Symbol
的符号的地址传递给一个函数,该函数的相应参数声明为指向 const
的指针。这意味着该函数不会修改对象。如果确实如此,那么原始对象应该在之前的某个时间被初始化。
604. 返回自动变量 ‘Symbol’ 的地址:函数正在传递名为 Symbol
的符号的地址。由于该对象是自动变量,并且自动变量的生命周期在返回后无法保证,这很可能是一个错误。你可能需要将值复制到一个全局变量中,并返回该全局变量的地址,或者考虑让调用者传递它自己的变量地址给被调用的函数。
605. 指针能力的提升(上下文):通常是将一个指向 const
的指针分配给一个普通指针引起的。例如:
int *p;
const int *q;
p = q; /* 605 */
如果使用类型转换,则不会出现此消息,如下所示:
p = (int *) q;
该消息表明能力的增加,因为通过 p
可以修改 q
指向的 const
对象。此消息也可能用于 volatile
修饰符以及 const
修饰符,并且可能针对任意指针深度(如指向指针、指向数组的指针等)给出。
如果指针级别超过一级,事情会变得更加复杂。例如:
const char **ppc;
char **pp;
pp = ppc; /* 605 - 显然不安全 */
ppc = pp; /* 605 - 看起来安全但实际上不安全 */
直到最近,C 社区才意识到将 pp
赋值给 ppc
是危险的。问题在于,在上述赋值后,可以通过 ppc
间接地分配一个指向 const char
的指针,并通过 pp
访问和修改该 const char
。
消息中提到指针能力的增加,这似乎与直觉相悖,因为间接指针的能力较小。然而,指针的赋值并没有销毁原来的指针,两个指针的组合代表了能力的净增加。
此消息也可能用于函数指针分配,当一个函数的原型包含的指针能力高于另一个原型中的相应指针时。这存在一种奇怪的反转,即较低能力的原型实际上转化为更值得信任的函数,因此能力更大(如特洛伊木马的例子)。例如:
void warrior( char * );
考虑以下函数:
void Troy( void (*horse)(const char *) );
Troy()
将使用一个它认为宝贵的参数调用 horse()
,相信 horse()
不会造成任何伤害。以前的编译器认为向目标添加 const
不会带来任何坏处,允许希腊人将 warrior()
传递给 Troy
,其余的事情便如历史所说。
606. 非 ANSI 转义序列:‘\String’:在字符或字符串文字中出现了不在批准列表中的转义序列,批准列表包括:
\' \" \? \\ \a \b \f \n \r \t \v
\八进制数字 \x十六进制数字
607. 在字符串中找到宏的参数 ‘Symbol’:所标识的名称出现在宏内的字符串或字符常量中,恰好与宏的形式参数同名,如下所示:
#define mac(n) printf( "n = %d,", n );
这只是一个巧合吗?ANSI 标准指出名称不会被替换,但由于许多 C 编译器确实会替换这些名称,因此这种构造是可疑的。检查宏定义,如果不希望替换,请更改参数名称。如果希望替换,请设置 +fps
标志(参数在字符串中),并使用 -e607
抑制消息。
608. 将值赋给数组参数:正在为一个被类型化为数组的参数赋值。对于赋值的目的,该参数被视为指针。通常,这类参数被类型化为指针而不是数组。如果这是你的编码风格,你应该抑制此消息。
609. 可疑的指针转换:正在进行两个大小不同(一个是远指针,另一个是近指针)的指针之间的赋值,但两者在其他方面是兼容的。
610. 可疑的指针组合:不同大小的指针(一个是远指针,另一个是近指针)正在进行比较、减法或在条件表达式中配对。这是可疑的,因为通常参与这些操作的指针应具有相同的大小。
611. 可疑的类型转换:正在进行函数指针与对象指针之间的类型转换,或反之。这被 ANSI 标准视为可疑。如果这不是用户错误,可以抑制此警告。
612. 预期声明符:声明中只有存储类和类型。这几乎肯定是一个错误,因为只有在结构体、联合体或枚举的情况下,类型不带声明符才有意义,但在这种情况下,不会使用存储类。
613. 在操作符 ‘String’ 的左/右操作数中可能使用了空指针 ‘Symbol’:从先前的语句中收集的信息显示,可能在不合适的上下文中使用了空指针(值为 0 的指针)。这些上下文包括:一元 *
,指针递增(++
)或递减(--
),指针与数字的加法,两个指针的减法。在二元操作符的情况下,使用 left
或 right
来指明哪个操作数是空指针。Symbol
标识了可能为空的指针变量。参见消息 413 和 794。
614. 自动聚合初始化器不是常量:自动聚合的初始化器通常由一组常量值表达式组成。然而,某些编译器可能允许在此上下文中使用变量,在这种情况下,你可以抑制此消息。
615. 自动聚合初始化器具有副作用:此警告类似于 614。自动聚合(数组、结构和联合体)通常通过一组没有副作用的常量值表达式进行初始化。编译器可能支持副作用,如果是这样,你可以选择抑制此消息。
616. 控制流进入 case/default 语句:控制流可能从上方进入一个 case
语句或 default
语句。这是故意的吗?还是程序员忘记了插入 break
语句?如果这是故意的,请在被标记的语句之前放置一条注释,例如:
case 'a': a = 0;
/* fall through */
case 'b': a++;
请注意,如果一个 case
仅仅跟随另一个 case
而没有插入其他语句,则不会发出消息。此外,必须确实有可能从上方进入。
617. 字符串同时是模块和包含文件:该文件同时作为模块和包含文件使用。这是一个错误吗?与错误 306(重复模块)不同,这只是一个警告,仍会尝试处理该文件。
618. 在类型之后指定了存储类:在指定类型之后发现了存储类说明符(如 static
、extern
、typedef
、register
或 auto
)。这在语法上是合法的,但不推荐使用。你可以将存储类说明符放在类型之前,或者抑制此消息。
619. 指针能力的丢失(上下文)(指向指针):一个远指针正被赋值给一个近指针,可能发生在赋值语句、隐式赋值(如初始化、返回语句或在有原型的情况下传递参数)中。此类赋值常常是当实际段不等于默认数据段时出错的根源。如果你确信远指针的段与默认数据段相同,可以使用类型转换来抑制此消息。
620. 可疑的常量(是 L 还是数字 1?):一个常量以小写字母 ‘l’ 结尾。这是打算使用数字 1 吗?这两个字符看起来非常相似。为了避免误解,使用大写字母 ‘L’。
621. 标识符冲突(符号 ‘Name’ 与符号 ‘Name’ 位于 ‘String’):两个符号出现在同一个命名空间中,并且在前几个字符内是相同的。这是由选项 -idlen(count,option)
设置的字符计数引发的。参见 -idlen
。
622. 参数编号 Integer 的大小与格式不一致:scanf
、fscanf
或 sscanf
函数中的某个参数的大小与格式说明符不匹配。例如:
int far *p;
scanf( "%d", p );
在默认内存模型下,这种情况会触发此警告。
623. 重新定义符号 ‘Symbol’ 的存储类 (TypeDiff) 与位置冲突:某个跨模块符号在一个模块中被定义为 typedef
,而在另一个模块中是普通符号。这是合法的,但可能会引起混淆。程序员是否有意这么做?
624. typedef
符号 ‘Symbol’ 的重新声明 (TypeDiff) (位置):一个符号在两个不同的模块中以不同的方式声明为 typedef
。技术上这并不违反规范,但并不是一个好的编程习惯。
625. 自动符号 ‘Symbol’ 具有不寻常的类型修饰符:一些类型修饰符如 far
、near
、fortran
等不适合用于自动变量。
626. 参数编号 Integer 的类型与格式不一致:printf
、fprintf
或 sprintf
函数的某个参数的类型与格式不一致。虽然数量的大小合适,但类型不正确。可以考虑将该数量强制转换为正确的类型。如果认为没有问题,也可以抑制此消息,因为更多显著的违规会被警告 559 捕获。
627. 参数编号 Integer 的间接对象与格式不一致:scanf
、fscanf
或 sscanf
的某个参数的类型与格式不匹配。然而,该参数是指针,并且指向的数量大小是预期的。
628. 函数 ‘Symbol’ 没有提供参数信息 (位置):调用了指定函数,但没有提供参数信息。参数信息可以来自原型或函数定义。这通常发生在老式函数声明中,该声明表明函数在库中,但没有给出该函数的原型,也没有在标准库文件中提供任何参数信息。如果你正在生成 lint 对象模块,则不会出现此消息,因为该对象模块可能会在稍后的时间与库文件进行比较。
629. 函数 ‘Symbol’ 的静态类为非标准:在函数声明中发现了静态类,但该声明位于另一个函数内。静态类只允许用于文件作用域的声明(即在任何函数之外)。要么将声明移到函数外,要么将 static
改为 extern
;如果选择第二种方式,请确保在文件作用域内已有 static
声明。尽管这种结构在技术上不可移植,但许多编译器确实容忍它。如果你抑制此消息,PC-lint/FlexeLint 将其视为适当的函数声明。
630. 对符号 ‘Name’ 的引用不明确:如果设置了 +fab
标志,则如果两个结构包含相同的成员名(不一定是不同类型的结构),并且在引用此成员名时省略了某个中间的(用于消歧的)名称,则会发出此警告。
631. 标记 ‘Symbol’ 在位置定义的方式不同:struct
、union
或 enum
标记 Symbol
在不同的作用域内被不同地定义。这不一定是错误,因为 C 允许重新定义,但这可能是潜在的错误来源。通常不推荐这种编程实践。
632. 在上下文中对强类型 ‘Name’ 进行赋值:某个赋值(或隐式赋值,上下文指示哪种情况)违反了 -strong(A...
选项所要求的强类型检查。
633. 从强类型 ‘Name’ 赋值的操作在上下文中不一致:某个赋值(或隐式赋值,上下文指示哪种情况)违反了 -strong(X...
选项所要求的强类型检查。
634. 在相等或条件操作中出现强类型不匹配(类型 ‘Symbol’):相等操作(==
或 !=
)或条件操作(? :
)违反了 -strong(J...
选项所要求的强类型检查。可以使用 Je
标志来抑制此消息。
635. 正在重置强类型的父类型 ‘Symbol’,旧父类型等于类型 ‘Symbol’:给定符号的强父类型正在重置。这是通过 -parent
选项或 typedef
语句执行的。请注意,这不一定是错误;系统正在提醒你旧的链接正在被覆盖。
636. 指向强类型 ‘Name’ 的指针与另一种类型相比:两个指针正在比较,且在第一级指针下方的类型存在强类型冲突。例如:
/*lint -strong(J,INT) */
typedef int INT;
INT *p;
int *q;
if( p == q ) /* 警告 636 */
将触发此警告。可以使用 Je
或 Jr
标志,或同时使用两者来抑制此消息。
637. 对强类型 ‘Symbol’ 的索引期望类型 ‘Symbol’:当检测到与 -index
选项不一致时,会发出此消息。下标的类型不是消息中提到的预期类型(第一个类型),也不与其类型层次结构中的其他类型相等。
638. 强类型 ‘Name’ 在关系操作中不匹配:关系操作(>=
、<=
、>
、<
)违反了 -strong(J...
选项所要求的强类型检查。此消息可以通过使用 Jr
标志来抑制。
639. 强类型 ‘Name’ 在二元操作中不匹配:除了相等和关系操作之外的二元操作违反了 -strong(J...
选项所要求的强类型检查。可以通过使用 Jo
标志来抑制此消息。
640. 布尔上下文中预期的强类型 ‘Name’:布尔上下文中期望的类型由 -strong(B...
选项指定。
641. 将枚举转换为整数:在需要计算的上下文中使用了枚举类型,例如作为算术操作的参数,或者与整数参数进行比较。如果你使用枚举的整数模型(+fie
),此警告将被抑制,但会因此丧失一些有价值的类型检查。一个中间策略是仅关闭此警告。将整数赋值给枚举仍会被捕获。
此警告不会针对没有变量的无标签枚举发出。例如:
enum {false, true};
此枚举不能用作单独的类型。PC-lint/FlexeLint 认识到这一点,并将 false
和 true
视为算术常量。
642. 格式字符 ‘Char’ 不受 wsprintf
支持:这意味着你正在使用类似 -printf(w...
的选项,并且正在使用 Microsoft Windows
函数 wsprintf
不支持的格式字符。如果你实际上并未使用 wsprintf
,而是使用了 w
标志来获取远指针,你应该关闭此消息。
643. 指针类型转换导致精度损失:将远指针转换为近指针。这样的类型转换曾经给 Windows 程序员带来了灾难性的后果。如果你确实需要进行这种类型转换,可以分阶段进行。首先将指针转换为一个长整型(即某个可以容纳该指针的整数类型),然后再转换为较短的值,系统不会发出警告。
644. 变量 ‘Symbol’ (位置) 可能尚未初始化:某个自动变量可能在使用之前没有被赋值。
645. 符号 ‘Symbol’ (位置) 可能尚未初始化:在被传递给一个期望接收 const
对象指针的函数之前,某个自动变量有条件地被赋值。参见警告 603,了解此类构造的危险。
646. 循环内的 case/default
可能放置错误:在 for
、do
或 while
循环内发现了一个 case
或 default
语句。这是故意的吗?至少,这是一种不好的编程风格。
647. 可疑的截断:当涉及 int
或 unsigned int
的操作发生截断时,该消息会被发出,操作的结果随后被转换为 long
。该消息仅针对 int
小于 long
的系统。例如:
(long) (n << 8)
如果 n
是 unsigned int
,可能会触发此消息,而下面的代码则不会:
(long) n << 8
在第一个例子中,移位在 int
精度下完成,高位的 8 位被丢失,尽管存在对较大精度类型的转换。在第二个例子中,移位后的位保持不变。
648. 计算操作时常量溢出:String:在计算常量表达式时检测到算术溢出。例如,如果 int
是 16 位,则 200 * 200
会导致溢出。String
表示引发溢出的操作,可能是:加法、无符号加法、乘法、无符号乘法、取负、左移、无符号左移、减法或无符号减法。
要抑制此消息,可以为特定的常量操作提供显式截断。例如,如果你想将整数 20000
的低 8 位移到 16 位 int
的高字节中,左移会触发此警告。然而,先截断再移位则是可以的。以下代码示例说明了这一点,假设 int
是 16 位的:
20000u << 8; /* 648 */
(0xFF & 20000u) << 8; /* OK */
如果使用类型转换进行截断,可能会将无符号表达式转换为有符号表达式。例如,以下代码会发出警告(假设 int
是 16 位):
(unsigned char) 0xFFFu << 8 /* 648 */
这是因为在移位之前 unsigned char
被提升为 int
,结果是负数。你需要恢复表达式的无符号性质,如下所示:
(unsigned) (unsigned char) 0xFFF << 8 /* OK */
649. 常量右移时符号填充:在评估常量表达式时,负整数右移导致符号填充已被移出的位。如果这是你想要的行为,抑制此错误即可,但请注意,符号填充是依赖于具体实现的。
650. 操作符 ‘String’ 的常量超出范围:在比较操作或相等测试中(或隐含的相等测试,如 case
语句),某个常量操作数超出了另一个操作数的范围。例如,如果将 300
与 char
变量进行比较,并且 char
是有符号的(8 位),则会发出此警告。此外,如果 char
是有符号的,你在比较时使用了大于 127
的整数,也会收到此消息。可以通过强制转换来修复此问题。例如:
if( ch == 0xFF ) ...
if( (unsigned char) ch == 0xFF ) ...
如果 char
是有符号的(未设置 +fcu
),第一个语句会收到警告,且永远不会成功。第二个语句则会抑制警告并修正错误。
PC-lint/FlexeLint 将考虑一些操作数的有限精度,例如位域和枚举类型。此外,PC-lint/FlexeLint 会利用一些限制操作数精度的计算。例如:
if( (n & 0xFF) >> 4 == 16 ) ...
会收到此警告,因为左侧的表达式限制为 4 位精度。
651. 潜在的令人困惑的初始化器:正在处理一个复杂聚合的初始化器,发现某些子聚合被括起来了,而有些则没有。ANSI 推荐使用“最小括号”初始化器(即没有内部大括号)或“全括号”初始化器(即所有内部聚合都用大括号括起来)。
652. 符号 ‘Symbol’ 之前在位置已声明的 #define 重定义:尝试为之前已声明的符号定义宏。例如:
int n;
#define n N
会引发此警告。会检查先前的局部和全局变量、函数和 typedef
符号、结构、联合和枚举标签。不会检查结构和联合成员。
653. 可能失去小数部分:当两个整数相除并赋值给浮点数变量时,小数部分丢失了。例如,虽然:
double x = 5 / 2;
看起来像是将 2.5
赋值给 x
,实际上赋值的是 2.0
。为了确保不会丢失小数部分,可以将至少一个操作数强制转换为浮点类型。如果你确实希望进行截断,可以在将结果赋值给浮点数之前将其转换为整型(int
或 long
)。
654. 选项 String 已过时;请使用 -width(W,I):-w
选项现在用于设置警告级别,不应再用于指定错误消息的宽度。改为使用 -width
,并使用相同的参数来设置宽度。例如,要将警告级别设置为 3,请使用 -w3
,而不是 -w(3)
。
655. 使用按位操作符时,兼容的枚举类型:使用按位操作符(|
、&
或 ^
)来组合两个兼容的枚举类型。结果的类型被视为枚举类型。这被认为是严格模型的一点小偏差,可以选择抑制此警告。
656. 使用算术操作符时,兼容的枚举类型:使用算术操作符(+
或 -
)来组合两个兼容的枚举类型。结果的类型被视为枚举类型。这被认为是严格模型的一点小偏差,可以选择抑制此警告。
657. 不寻常的(不可移植的)匿名结构或联合体:没有声明符的结构或联合体被视为匿名的。然而,C++ 和其他 C 方言支持的匿名联合需要无标签联合体。带标签的联合体以及带标签或无标签的结构通常不被支持为匿名的。
658. 假定为匿名联合(请使用 +fan 标志):发现了一个没有声明符的联合。这是打算定义匿名联合吗?如果是这样,应该通过 +fan 标志启用匿名联合。对于 C++,此标志会自动激活。
659. 在结构/联合/枚举声明中的 ‘}’ 后无任何内容:出现了结构、联合、类或枚举定义,且在同一行上的闭合 }
后没有跟随其他符号。这看起来很可疑。缺少定义后的分号会导致奇怪且难以理解的消息。如果故意省略了分号,请将后续的符号放在 }
的同一行。至少要在 }
后面加上注释。
660. 选项 ‘String’ 请求删除一个不在列表中的扩展:许多选项使用 ‘-’ 前缀来从列表中删除元素,使用 ‘+’ 来添加元素。例如,要添加最不寻常的扩展 .C++
以表示处理带有该扩展名的 C++ 文件,程序员应使用选项:
+cpp(.C++)
但是,如果错误地使用了前导 ‘-’,将会发出此警告。
661. 可能访问超出数据末尾的指针 (‘Integer’ 超出数据末尾) 通过操作符 ‘String’:可能已访问了超出数据末尾的指针。有关参数 Integer 和 String 的说明,请参见消息 415。例如:
int a[10];
if( n <= 10 ) a[n] = 0;
这里程序员可能应该写作 n < 10
。此消息与消息 415 和 796 类似,但不同之处在于概率的高低。
662. 可能创建超出数据末尾的指针 (‘Integer’ 超出数据末尾) 通过操作符 ‘String’:可能创建了一个超出数据末尾的指针。有关参数 Integer 和 String 的说明,请参见消息 415。例如:
int a[10];
if( n <= 20 ) f( a + n );
这里似乎创建了一个非法指针,但 PC-lint/FlexeLint 无法确定。参见消息 416 和 797。
663. 可疑的数组到指针转换:以下情况可能会触发此警告:
struct x { int a; } y[2];
... y->a ...
这里,程序员忘记给数组加索引,但错误通常不会被检测到,因为数组引用会自动且隐式地转换为指向数组第一个元素的指针。如果你确实要访问第一个元素,应该写成 y[0].a
。
664. 逻辑或 (||) 或逻辑与 (&&) 左侧不返回:在某个操作符的左侧发现了一个退出函数,这意味着右侧将永远不会被执行。例如:
if( (exit(0), n == 0) || n > 2 ) ...
由于 exit
函数不会返回,因此控制流永远不会进入右侧操作数。
665. 宏 ‘Symbol’ 中的未加括号参数 Integer 被传递了一个表达式:将表达式传递给宏参数,且该参数未加括号。例如:
#define mult(a,b) (a*b)
... mult( 100, 4 + 10 )
这里程序员可能认为 4+10
被当作一个整体与 100
相乘,但结果却是 100*4+10
,这与预期大不相同。推荐的解决方法是给这样的参数加括号,如下所示:
#define mult(a,b) ((a)*(b))
此消息不会在宏定义时发出,因为在某些情况下不适合为参数加括号。例如,以下宏期望传递一个操作符作为参数:
#define check(x, op, y) if( ((x) op (y)) == 0 ) print( ... )
666. 含有副作用的表达式传递给宏 ‘Symbol’ 的重复参数 Integer:某个宏的重复参数被传递了一个带有副作用的表达式。例如:
#define ABS(x) ((x) < 0 ? -(x) : (x))
... ABS( n++ )
虽然 ABS
宏正确地定义为返回其参数的绝对值,但参数 x
的重复使用意味着实际参数 n++
会被重复评估两次,导致变量 n
被递增两次。任何包含函数调用的表达式也被认为具有副作用。
667. 符号 ‘Symbol’ 的限定符使用不一致(类型 ‘Type’ 与 ‘Type’)与位置冲突:某个符号的声明与该符号的先前声明不一致。声明中存在表面上的差异,但由于所选的内存模型,实际并无差异。例如,在大内存模型下,一个声明将外部符号 alpha
声明为远指针,而另一个声明省略了内存模型规范。
668. 可能向函数 ‘Symbol’ 传递了一个空指针,参数为 Context:可能正在将一个空指针传递给函数 Symbol
。引发问题的参数由 Context
指定。该函数可能是设计为不接收空指针的库函数,或者用户通过 -function
选项指定不接收空指针的用户函数。
669. 函数 ‘Symbol’ 可能发生数据溢出,参数 Integer 超出参数 Integer 的范围:该消息用于数据传输函数(如 memcpy
、strcpy
、fgets
等),当第一个引用的参数(或多个参数)指定的数据大小可能超出第二个引用的缓冲区大小时会触发此消息。此消息也可以通过 -function
选项应用于用户自定义函数。
670. 函数 ‘Symbol’ 的参数 Integer 超出范围,可能访问数组之外的数据:对于某些库函数(如 fwrite
、memcmp
等),如果指定的数据长度超过了数据的实际大小,则会发出此消息。例如,如果在 fwrite
调用中指定的数据长度超过了实际数据的大小,该消息将会触发。函数由 Symbol
指定,参数通过参数编号标识。
671. 可能将负值(Integer)传递给函数 ‘Symbol’,参数为 Context:正在将一个可能为负值的整数传递给只期望正值的函数的特定参数。此消息包含函数名称(Symbol
)、可疑的值(Integer
)以及参数编号(Context
)。该函数可能是设计为只接受正值的标准库函数,如 malloc
或 memcpy
(第三个参数),也可能是用户通过 -function
或 -sem
选项指定的函数。参见消息 422 了解示例和进一步的解释。
672. 可能在赋值给指针 ‘Symbol’ 时发生内存泄漏:正在对指针变量(由 Symbol
指定)进行赋值,而该指针可能已经持有一个已分配对象的地址,并且尚未释放该地址。未释放的内存分配被视为“内存泄漏”。由于只有某些路径会导致泄漏,因此该泄漏被视为“可能的”。
673. 可能不合适的内存释放操作(Name1)用于 ‘Name2’ 类型数据:该消息表明,某种类型的释放操作(如 free()
、delete
或 delete[]
)可能不适用于正在释放的数据。数据的类型可能是以下之一:malloc
、new
、new[]
、static
、auto
、member
、modified
或 constant
。消息中的“可能”表示只有部分执行路径显示出数据与分配不一致。
674. 通过变量 ‘Symbol’ 返回自动变量的地址:指针变量持有的值包含一个自动变量的地址。返回栈上某个项目的地址通常是不正确的,因为返回后,该栈空间可能会被覆盖。
675. 选项 ‘String’ 中的 ‘Name’ 没有先前的语义关联:-function
选项用于将语义从第一个参数传递到后续参数。然而,发现第一个参数 Name
没有语义。
676. 操作符 ‘String’ 中可能存在负下标(Integer):正在将一个可能为负值的整数添加到一个数组或分配区域的指针中(如通过 malloc
或 operator new
等分配)。此消息不会针对未知来源的指针发出,因为负下标在某些情况下是合法的。
677. sizeof
用于预处理器语句中:尽管许多编译器在预处理阶段支持使用 sizeof
,但这不是 ANSI C 或 C++ 标准的一部分。
678. 成员 ‘Symbol’ 的字段长度 (Integer) 对枚举的精度 (Integer) 来说太小:发现一个位字段太小,无法支持枚举的所有值。例如:
enum color { red, green, yellow, blue };
struct abc { enum color c:2; };
在这种情况下,不会发出消息,因为 color
枚举的四个值正好适合 2 位。然而,如果再增加一个颜色值,则会触发警告 678,提醒程序员这一不理想且危险的情况。
679. 将整数表达式与指针组合时可能发生可疑的截断:当涉及整型的运算发生在指针之前,且指针的精度大于整型表达式时,该消息会发出。例如:
// lint -sp8 指针为 8 字节
// lint -si4 整数为 4 字节
char *f( char *p, int n, int m ) {
return p + (n + m); // 警告 679
}
根据 C/C++ 规则,n + m
的加法在其上下文之外独立完成,并在整数精度下执行。任何溢出都会被忽略,即使指针的较大精度可以容纳该溢出。如果表达式改为 p + n + m
,它解析为 (p + n) + m
,则不会发出警告。
如果表达式是 p + n * m
,要抑制该警告,需要使用类型转换。如果 long
与指针大小相同,你可以使用以下表达式:
return p + ((long) n * m);
680. 转换为指针时在算术表达式中发生的可疑截断:将某个算术表达式强制转换为指针,并且该指针的大小大于表达式的大小。在计算表达式时,任何溢出都将丢失,即使指针类型可以容纳丢失的信息。要抑制该消息,可以将其中一个操作数强制转换为足够大的整数类型以容纳指针。或者,如果确定没有问题,可以先将表达式转换为整数类型,然后再转换为指针类型。参见消息 647、776、790 和 679。
681. 循环未进入:控制循环的表达式(在 while
子句中的表达式或 for
子句中的第二个表达式)最初评估为 0,因此似乎循环永远不会进入。
682. sizeof
应用于类型为已知大小数组的参数 ‘Symbol’:如果一个参数类型为数组,它将默默地提升为指针。对这样的数组取大小实际上会返回指针的大小。考虑以下示例:
unsigned f( char a[100] ) { return sizeof(a); }
在这里,看起来函数 f()
会返回值 100
,但实际上它返回的是指针的大小,通常为 4。
683. 函数 ‘Symbol’ 被定义为 #define:每当函数名称被定义为宏时,都会发出此消息。例如:
#define strlen mystrlen
将引发该消息。问题在于 strlen
的语义将丢失。可以使用 -function(strlen, mystrlen)
选项将语义从 strlen
转移到 mystrlen
。该消息会针对内置函数(具有内置语义)或用户定义的语义发出。如果该函数被定义为类似名称的函数,如带有前后下划线的函数,则不会发出该消息。例如:
#define strlen __strlen
将不会触发此消息,但会发出 Info 828
。
684. 将自动变量的地址传递给调用者空间:通过赋值,将一个自动变量的地址传递到由调用者指定的位置。例如:
void f( int *a[] ) {
int n;
a[1] = &n;
}
在此示例中,自动变量 n
的地址被传递到数组 a
的第二个元素。这很可疑,因为在函数 f()
返回后,数组将包含指向一个生命周期已结束的变量的指针。可能这种情况是无害的,因为 f()
的调用者可能只是传递一个临时工作空间,在函数返回后即丢弃。如果确实如此,可以使用选项 -efunc(684,f)
来抑制该消息。参见警告 604。
685. 关系操作符 ‘String’ 总是评估为 ‘String’:第一个 String
是 ‘>’、‘>=’、‘<’ 或 ‘<=’ 中的一个,用来标识关系操作符。第二个 String
是 ‘True’ 或 ‘False’。该消息在一个表达式与常量进行比较时发出,表明由于表达式的精度,测试将总是成功或总是失败。例如:
char ch;
...
if( ch >= -128 ) ...
在这个例子中,char
类型的 ch
具有 8 位有符号整数的精度(假设 fcu
标志处于关闭状态),因此它的取值范围是 -128 到 127。因此,该测试将始终为 True
。
注意,技术上讲,ch
在比较之前会被提升为 int
,但在进行此类比较时,我们只考虑其底层精度。另一个示例,如果 u
是 unsigned int
类型,则:
if( (u & 0xFF) > 0xFF ) ...
也会触发 685
,因为左侧表达式的有效精度为 16 位。
686. 选项 ‘String’ 因 ‘Name’ 显得可疑:某个选项因某种原因被认为是可疑的。原因由 Name
中的代码指定。在撰写本文时,唯一的原因代码是“未配对的引号”。
687. 可疑的逗号操作符使用:在 if
、else
、while
或 for
子句后面出现了一个未加括号的逗号操作符。例如:
if( n > 0 ) n = 1,
n = 2;
因此,逗号可能被误认为是分号,从而导致隐蔽的错误。
如果语句包含在大括号中,或者表达式包含在括号中,则不会发出该消息。
688. 在预处理器条件语句中使用了类型转换:在预处理器条件语句(如 #if
或 #elif
)中使用了类型转换。例如,你可能写了:
#define VERSION 11.3
...
#if (int) VERSION == 11
...
#endif
这种类型转换不被 C 和 C++ 标准所允许。
689. 忽略了似乎是注释结尾的符号:在注释之外发现了 */
符号对。例如:
void f( void*/*comment*/ );
这被视为等同于:
void f( void* );
也就是说,在 *
和 /
之间隐含插入了一个空格。要避免此消息,只需在这两个字符之间显式添加一个空格。
690. 可能通过操作符 ‘String’ 访问了终止的 nul 字符之后的指针 (Integer):访问终止的 nul 字符之后的数据常常是程序员错误的标志。例如:
char buf[20];
char c;
strcpy( buf, "a" );
if( i < 20 )
c = buf[i]; // 合法但可疑
参见第 13.3 节中的警告 448 和第 13.4 节中的信息 836。
691. 可疑的反斜杠使用:反斜杠字符以可能产生意外结果的方式使用。通常发生在宏定义中,例如:
#define A b \ // 注释
程序员可能认为宏定义会继续到下一行。但标准指出,如果存在注释,换行符不会被删除。正确的写法应为:
#define A b /* 注释 */ \
692. 十进制字符 ‘Char’ 跟随八进制转义序列 ‘String’:在 String
中发现了 ‘8’ 或 ‘9’,它们紧跟在八进制转义序列(最多两位八进制数字)之后,例如:
"\079"
包含两个字符:八进制数字 7(ASCII BEL),后跟字符 ‘9’。代码的阅读者(甚至可能是程序员自己)可能会被误导,认为这是单个字符。如果这是程序员的本意,也可以改写为:
"\07" "9"
这样就不会有误解。
另一方面:
"\1238"
不会触发警告,因为默认情况下,程序员应该知道八进制转义序列不能超过四个字符(包括初始反斜杠)。
693. 在字符串文字中,字符 ‘Char’ 紧接 ‘String’ 之后的十六进制数字可疑:在 String
中发现了一个看似可疑(但实际上不是)的十六进制转义序列;确切地说,它是一个空字符,后跟字符 “x”,后面是一些十六进制数字,例如:
"\0x62"
程序员可能想要输入的是 "\x62"
。如果你确实需要该序列,可以改写为:
"\0" "x62"
这样不会触发此警告。
694. 常量 ‘String’(精度 Integer)的类型依赖于方言:需要使用无符号长整型的十进制整数常量的类型取决于你使用的 C 或 C++ 方言。例如,常量 3000000000
需要 32 位来表示。如果 long
为 32 位,则该常量在 C90 中被视为 unsigned long
,在 C99 中被视为 long long
,而在 C++ 中是未定义的。
你可以通过应用明确的后缀来消除歧义。如果希望它是无符号的,可以使用 U
后缀。如果希望它是长长整型,可以使用 LL
后缀。如果你使用的是 C++,则启用 +fll
标志。
695. 未使用存储类说明符(建议使用 ‘static’)定义的内联函数 ‘Symbol’:在 C99 中,调用未带 static
或 extern
的 inline
函数的结果是不确定的。
示例:以下代码表示两个单元:
/* 在 module_1.c 中 */
void f() {}
/* 在 module_2.c 中 */
inline void f() {}
void g() { f(); } /* 这个 f() 是哪个? */
C99 标准规定,调用 g()
中的 f()
可能会执行 module_2.c
中的 f()
或 module_1.c
中的 f()
。
程序员可以通过使用 static
关键字避免混淆并提高可移植性。inline
也可以与 extern
一起使用以解决此问题;但是,我们建议使用 static
,因为在撰写本文时,更多编译器能够正确解释 static inline
。
696. 变量 ‘Symbol’ 的值 ‘String’ 超出了操作符 ‘String’ 的范围:该消息表示变量 Symbol
正在与一个操作数进行比较操作(使用 6 种比较操作之一),且该变量的值超出了操作数的范围。例如:
void f(unsigned char ch) {
int n = 1000;
if (ch < n) // 消息 696
...
在这个例子中,将会触发消息 696,指出 n
的值为 1000,超出了 ch
所能表示的范围(假设默认标量尺寸)。ch
是 unsigned char
类型,其值范围为 0 到 255,因此永远不会小于 1000。
697. 类布尔值应只与 0 进行相等比较:正在将一个类布尔值(quasi-boolean
)与非零值进行比较(使用 !=
或 ==
)。类布尔值是任何类型为强布尔类型的值,其可能不仅仅是 0 或 1。这点很重要,因为在 C 语言中,所有非零值都被认为是真值。例如:
/*lint -strong(AJXb, B) */
typedef int B;
#define YES ((B)1)
#define NO ((B)0)
B f(B a, B b) {
B c = (a == NO); /* OK,不会有警告 */
B d = (a == (b != NO)); /* == 会触发警告 697,但 != 不会 */
B e = (a == YES); /* 会触发警告 697 */
return d == c; /* 会触发警告 697 */
}
注意,如果 a
和 b
使用了真正的布尔类型(如 C++ 中的 bool
或 C99 中的 _Bool
),则不会触发此诊断信息。
698. 使用 realloc
不当可能会造成内存泄漏:检测到如下形式的语句:
v = realloc(v, ...);
问题在于,realloc
可能无法分配所需的存储空间。如果失败,它会返回 NULL
,但是由于原变量 v
已被覆盖,导致内存泄漏。因此,在分配失败时,原来的内存无法被释放。
信息性消息
701. 有符号整型(int)的左移:通常移位操作是在无符号操作数上执行的。当对 int
类型的有符号数进行左移时,可能会导致未定义行为。
702. 有符号整型(int)的右移:通常移位操作是在无符号操作数上执行的。对 int
类型的有符号数进行右移时,具体表现依赖于机器(符号填充 vs. 零填充)。
703. 有符号长整型(long)的左移:通常移位操作是在无符号操作数上执行的。当对 long
类型的有符号数进行左移时,可能会导致未定义行为。
704. 有符号长整型(long)的右移:通常移位操作是在无符号操作数上执行的。对 long
类型的有符号数进行右移时,具体表现依赖于机器(符号填充 vs. 零填充)。
705. 参数整数与格式不一致:printf
(或 fprintf
或 sprintf
)的参数与格式标识符可能不完全匹配。尽管数值大小合适,类型相似,但并不完全正确。例如,传递 long
型参数给 %d
或传递 int
给 %x
。你可以考虑将参数强制转换为正确类型。也可以忽略此信息,因为更严重的格式问题会通过警告 559 和 626 报告。
706. 参数的间接对象与格式不一致:scanf
(或 fscanf
或 sscanf
)的参数类型不符合预期的格式。然而,该参数是一个指针,指向的数量大小符合预期,但类型不完全相同。
707. 在字符串拼接中混合窄字符和宽字符字面量:例如:
const wchar_t *s = "abc" L"def";
拼接窄字符和宽字符字面量在 C90 和 C++2003 中的行为未定义。如果你的编译器支持这种组合,或者你使用支持该特性的 C/C++ 方言,可以忽略此消息,或者让拼接的字符串字面量匹配。
708. 联合类型的初始化:尝试初始化一个联合类型的值。在某些较老的 C 编译器中,可能不允许这种初始化,因为会出现歧义:应该初始化哪个成员?标准解释是初始化联合的第一个子类型。
712. 精度损失(上下文:从较大类型到较小类型):在两个整数类型之间进行赋值(或隐式赋值)时,目标类型比源类型小,可能会导致精度损失。使用强制类型转换可以忽略此消息。
713. 精度损失(上下文:从无符号类型到有符号类型):将无符号量赋值给有符号量时,可能会导致精度损失,例如从 unsigned int
转换为 int
。使用强制类型转换可以忽略此消息。
714. 符号 ‘Symbol’(位置)未被引用:定义了外部变量或外部函数,但没有被引用。在单元测试模式下(使用 -u
选项)此消息会被忽略。
715. 符号 ‘Symbol’(位置)未被引用:声明了形式参数,但没有在函数内部使用。
716. while(1)
循环:发现了 while(1)
形式的循环结构。虽然在期望布尔值的上下文中这表示一个常量,但它可能反映了一种编程策略,即通过此结构前缀表示无限循环。因此,给出了单独的编号,并将其归入信息性类别。更常见的无限循环前缀形式是 for(;;)
。
717. do ... while(0)
结构:在期望布尔值的上下文中,这种结构可能是程序员故意将一系列语句封装为一个单一语句的尝试,因此它被赋予了单独的错误消息。例如:
#define f(k) do {n=k; m=n+1;} while(0)
允许在条件语句中使用 f(k)
,例如:
if(n > 0) f(3);
else f(2);
如果这是有意的,可以使用 -e717
来忽略此消息。
718. 符号 ‘Symbol’ 未声明,假定返回 int
:引用了一个函数,但该函数尚未在当前模块中声明或定义。这不一定是错误的,你可以选择忽略此消息。注意,添加一个声明到其他模块不会抑制此消息。要忽略它,必须在当前处理的模块中声明。
719. printf/scanf
函数的参数过多:传递给 printf/scanf
家族函数的参数比格式字符串中指定的多。与警告 558 类似,它警告传递的参数过少。此消息分配给信息性类别,因为额外的参数只是被忽略。
720. 布尔条件中使用赋值:在需要布尔值的上下文(如 if()
或 while()
语句中,或作为 &&
或 ||
的操作数)中发现了赋值操作。这可能是合法的,但也可能是将 =
错误用作 ==
的结果。
721. 可疑的 ;
使用:在 if(e)
语句的右括号后立即发现了一个分号。这可能会被忽略或与语句终止符号混淆。如果分号与括号之间有至少一个空格,则消息将被抑制。更好的方式是将其放在单独一行上。参见消息 548。
722. 可疑的 ;
使用:在 while(e)
或 for(e;e;e)
语句的右括号后立即发现了一个分号。这可能会被忽略或与语句终止符号混淆。如果分号与括号之间有至少一个空格,则消息将被抑制。更好的方式是将其放在单独一行上。
723. 可疑的 =
使用:预处理器定义以等号(=
)开头。例如:
#define LIMIT = 50
这是否是有意的?或者是程序员在编写时误将其视为赋值操作?
725. 期望与上一行的正向缩进:当前行与指示行的缩进相同,而不是相对于它进行缩进。指示行通常是一个控制结构的引导语句,控制结构中的语句应相对于它缩进。如果程序中的制表符不等于 8 个空格,则应使用 -t
选项。
726. 忽略多余的逗号:在枚举的右括号前发现了一个逗号,这在 ANSI 规范中不是合法的结构。逗号将被忽略。
727. 符号 ‘Symbol’(位置)未显式初始化:未显式初始化的静态变量(局部于函数)。此消息表明在定义对象时没有初始化器,或者在使用之前没有直接赋值给该对象。由于静态变量默认会被初始化为 0,因此此消息并不一定是错误。但该消息有助于指示你可能忘记初始化为其他值的变量。对于那些需要初始化为 0 的变量,建议使用显式初始化。例如:
static int n = 0;
对于将动态初始化的变量,不要使用显式初始化,例如:
static int m;
此消息会针对数组、结构或联合类型的所有成员或元素,如果没有赋值,则会发出该消息。
728. 符号 ‘Symbol’(位置)未显式初始化:未显式初始化的模块内静态变量(具有文件作用域的静态变量)。更多细节请参阅消息 727 的相关描述。
729. 符号 ‘Symbol’(位置)未显式初始化:未显式初始化的模块间变量(外部变量)。更多细节请参阅消息 727 的相关描述。在单元测试时此消息会被忽略(使用 -u
选项)。
730. 布尔类型作为函数参数:布尔类型作为函数参数传递。这是否是有意的?或者可能是复杂条件语句引起的混乱。经验丰富的 C 程序员通常会忽略此消息。只有在相关参数未声明为 bool
时,才会出现此消息。
731. 布尔类型作为相等/不相等操作的参数:布尔类型作为 ==
或 !=
操作符的参数。例如:
if( (a > b) == (c > d) )
用于检查两个不等式是否具有相同的布尔值。虽然这是一种不常见的布尔值用法,但它可能是故意的,因为这是有效实现等价或异或的唯一方式。由于可能的合法用途,此构造被归为信息性消息。如果将布尔类型强制转换为其他类型,则不会给出此消息。
732. 丢失符号(上下文:类型转换):从有符号类型赋值(或隐式赋值)给无符号类型时,符号位可能会丢失。例如:
u = n; // Info 732
u = 4; // OK
其中 u
是无符号类型,而 n
是有符号类型,只有第一条语句会触发消息。确保这是正确的,即被赋值的值永远不会为负数。然后使用强制转换(到无符号类型)来消除该消息。
733. 将自动变量 ‘Symbol’ 的地址赋值给外部作用域的符号 ‘Symbol’:自动变量的地址仅在声明该变量的块内有效。将该地址赋给了生命周期较长的变量,可能会引发问题。
734. 精度丢失(上下文:整数位到整数位):将较大的整数类型赋值给较小的类型时,可能会导致精度丢失。这里不包括符号位。例如,如果 ch
是 char
类型,而 n
是 int
类型:
ch = n;
这将触发消息,而:
ch = n & 1;
则不会。使用强制转换可以忽略此消息,例如:
ch = (char) n;
当子整数变量参与乘法或移位操作时,也可能收到警告,例如:
ch = ch << 2;
ch = ch * ch;
这些可以通过 +fpm
标志来抑制(操作符的精度受其操作数的最大值约束)。
735. 精度丢失(上下文:整数位到整数位):从 long double
转换为 double
时,可能会丢失精度。使用强制类型转换可以抑制此消息。该消息包括符号位在内。
736. 精度丢失(上下文:整数位到整数位):将具有更高精度的值赋值(或隐式赋值)给 float
类型时,可能会丢失精度。使用强制类型转换可以抑制此消息。该消息包括符号位在内。
737. 提升类型时符号丢失(类型到类型):无符号量与有符号量在二元操作符中结合(或作为三元条件操作符的第二和第三个参数)时,有符号量会隐式转换为无符号量。如果有符号量是无符号常量、布尔值或涉及位操作的表达式,此消息不会出现。例如:
u & ~0xFF;
其中 u
是无符号类型,而右侧的操作数是符号常量。该操作看起来像是无符号量,因此不会给出消息。此混合模式操作可能会导致 573 或 574 警告,取决于使用了哪个操作符。使用强制类型转换可以抑制该消息,但首先应确定有符号值是否可能为负数,或者无符号值是否能够适应有符号量的限制。
738. 符号 ‘Symbol’(位置)未显式初始化:未初始化的静态局部变量在传递给期望 const
指针的函数时是否是错误?还是程序员依赖于静态变量默认初始化为 0?使用显式初始化可以抑制此消息。更多信息请参见消息 727 和 603。
739. 字符串中包含三字母序列 ‘String’(静默更改):在字符串中发现了三字母序列(trigraph)。根据 ANSI 标准,三字母序列会被映射为一个字符。这种行为与过去不同,以前这种序列没有特殊处理。如果你不打算将这些字符映射为单一字符,可以在序列前加上反斜杠。如果你是有意使用三字母序列,可以忽略此消息。
740. 不常见的指针类型转换(不兼容的间接类型):将一个指针转换为另一种类型的指针,且两个指针都不是通用指针(如 char*
、unsigned char*
或 void*
),并且其间接类型截然不同。例如,将指向 int
的指针转换为指向 double
的指针,可能会引发此消息。该消息不会出现在仅仅签名不同的类型间转换(例如 unsigned int*
和 int*
),或仅有资格修饰符不同的类型间转换(例如 const int*
和 int*
)。此外,若间接类型是 union
,也不会触发此消息。
741. 不常见的指针类型转换(函数修饰符不同):将一个指针类型转换为另一种函数修饰符不同的指针类型(例如 pascal
、fortran
、cdecl
或 interrupt
)。如果这不是错误,可以先转换为通用指针(例如 void*
),以避免此消息。
742. 多字符字符常量:发现了包含多个字符的字符常量(例如 'ab'
)。这种构造在 C 中是合法的,但其数值取决于具体实现。如果字符数超过 int
的长度,会触发消息 25。
743. 负字符常量:定义了负值的字符常量。例如,在字节为 8 位的机器上,字符常量 \xFF
会被视为 -1(其类型为 int
)。根据 ANSI 标准,其值不是 0xFF
。
744. switch
语句没有 default
标签:switch
语句没有 default
标签。是否遗漏了?在许多编程组中,标准做法是总是包含 default
标签。这可以提高错误检测的能力并在早期发现问题。抑制此消息的一种方式是引入一个空的 default: break;
语句。如果你认为这会增加程序的开销,实际上它不会对代码长度产生任何影响。带上适当的注释,代码的可读性也会提高。
745. 函数 ‘Name’ 没有显式类型或类,假定为 int
:函数声明或定义中缺少显式的返回类型。这是否是故意的?
746. 调用函数 ‘Name’ 时没有原型:调用函数时,未在原型前声明。这并不意味着 PC-lint/FlexeLint 没有找到原型,只是原型在编译器无法识别的地方。如果你没有采用严格的原型约定,可以使用 -e746
抑制此消息。
747. 原型强制类型转换(上下文:类型到类型):原型中指定的类型与作为参数传递的类型有显著不同。通常两种类型是大小不同的算术类型,或者一种是浮点数而另一种是整数。该消息的目的是防止在不支持原型转换的编译器中转换失败。
748. 使用 setjmp
时,寄存器变量 ‘Symbol’ 被使用:在调用 setjmp
的函数中使用了寄存器变量。当发出 longjmp
调用时,寄存器变量的值可能无法预测。如果此消息未被抑制,该变量将被标记为未初始化。
749. 本地枚举常量 ‘Symbol’(位置)未被引用:模块中定义了枚举成员(名称为 Symbol
),但未在该模块中使用。‘本地’ 枚举成员是指未在头文件中定义的成员。与消息 754 和 769 中的全局枚举常量不同,本地枚举成员未被引用时,会发出此信息性消息。
750. 本地宏 ‘Symbol’(位置)未被引用:本地宏是指未在头文件中定义的宏。该宏在定义它的模块中未被引用。
751. 本地类型定义符 ‘Symbol’(位置)未被引用:本地类型定义符是指未在头文件中定义的 typedef
符号。该符号在其作用域内未被使用,无论它是文件作用域还是块作用域。
752. 本地声明符 ‘Symbol’(位置)未被引用:本地声明符是指模块文件中的声明符,而不是头文件中的声明符。该符号无论是在文件作用域还是块作用域内都未被引用。
753. 本地结构体、联合或枚举标记 ‘Symbol’(位置)未被引用:本地标记是指未在头文件中定义的结构体、联合或枚举标记。既然定义了它,为什么不使用它呢?标记的使用可以通过其任何成员的使用隐式表达出来。
754. 本地结构体成员 ‘Symbol’(位置)未被引用:模块中定义了结构体或联合的成员(名称为 Symbol
),但未在该模块中使用。本地成员是指未在头文件中定义的成员。更多信息请参考消息 768。
755. 全局宏 ‘Symbol’(位置)未被引用:全局宏是指定义在头文件中的宏。该消息针对非库头文件中定义的宏发出,且该宏未在程序的任何模块中使用。在单元测试中(使用 -u
选项),该消息会被忽略。
756. 全局类型定义符 ‘Symbol’(位置)未被引用:此消息针对非库头文件中定义的 typedef
符号发出,且该符号未在程序的任何模块中使用。在单元测试中(使用 -u
选项),该消息会被忽略。
757. 全局声明符 ‘Symbol’(位置)未被引用:此消息针对非库头文件中声明的对象发出,这些对象未在任何模块中使用。在单元测试中(使用 -u
选项),该消息会被忽略。
758. 全局结构体、联合或枚举标记 ‘Symbol’(位置)未被引用:此消息针对在非库头文件中定义的结构体、联合或枚举标记发出,且未在任何模块中使用。在单元测试中(使用 -u
选项),该消息会被忽略。
759. 头文件中的符号 ‘Symbol’(位置)可以从头文件移到模块中:此消息针对在非库头文件中声明的符号发出,若该符号未在定义模块之外引用,则可以将其移动到模块内部,从而减轻头文件的负担。此消息仅在多个模块被 lint 时发出。
760. 冗余宏 ‘Symbol’ 在位置 Location
处被相同定义:该宏之前已经以相同方式定义,因此是冗余的。
761. 冗余的类型定义符 ‘Symbol’ 之前已在位置 Location
处声明:typedef
符号在之前的位置已经声明,尽管声明是一致的,但你可能想删除第二个声明。
762. 冗余的符号 ‘Symbol’ 在位置 Location
处已声明:在同一作用域中发现了符号的一致声明。这次声明没有增加任何新的内容,可以删除。
763. 符号 ‘Symbol’ 的冗余声明在位置 Location
处已经声明:模块中一致地定义了 struct
、union
或 enum
的标记符号。可以删除重复的声明。
764. switch
语句没有 case
:发现了 switch
语句,但没有与其关联的 case
语句(可能包含或不包含 default
语句)。通常这是一种无用的构造。
765. 外部符号 ‘Symbol’(位置)可以设为静态:一个外部符号仅在一个模块中被引用,且未声明为静态(其类型也未使用微软关键字 __export
)。有些程序员倾向于尽可能将符号设为静态,因为这减轻了链接器的负担,并且能提供更好的文档说明。然而,你也可能希望符号保持外部链接,因为调试器通常只处理外部名称。通过宏,可以同时拥有两者的优势。
766. 头文件 FileName
未在模块 String
中使用:在指定的模块中直接包含了该头文件,但在处理该模块或任何包含的头文件时未使用它,因此可以将其删除。其未包含宏、typedef
、struct
、union
或 enum
标记符号或组件,也未声明。在某些情况下,早期包含的头文件可能已经包含了该文件。可以使用警告 537 来检测这种情况。
767. 宏 ‘Symbol’ 在另一个模块(位置 Location
)中的定义不同:在两个不同模块中处理的两个宏定义不一致。
768. 全局结构体成员 ‘Symbol’(位置)未被引用:在非库头文件中定义的结构体或联合的成员(名称为 Symbol
)未在程序的任何模块中使用。在单元测试中此消息会被忽略。因为结构体可能会在存储中重复,找到未使用的成员有助于节省存储空间。然而,许多结构体仅仅反映了一种访问存储的约定,对于某些程序来说,许多成员是未使用的。在这种情况下,接收此消息可能会令人困扰。一个方便的方法是将这样的结构体放置在库头文件中,或使用 ++flb
… --flb
包裹以强制将其视为库结构体。
769. 全局枚举常量 ‘Symbol’(位置)未被引用:在非库头文件中定义的枚举成员(名称为 Symbol
)未在程序的任何模块中使用。在单元测试中此消息会被忽略。程序员有时可能希望保留未使用的枚举常量,因此此消息与 768 消息(未使用的结构体成员)有所区分。有关选择性忽略此消息的方法,请参考消息 768。
770. 标记 ‘Symbol’ 在位置 Location
处被相同定义:在不同位置(通常是不同的文件)中一致地定义了 struct
、union
或 enum
标记符号。虽然这不是错误,但也不一定是好的编程实践。最好将这些常见的定义放入头文件中,这样它们可以在多个模块之间共享。这样可以避免出现此消息。如果在不同作用域中定义了不同的标记,则会收到警告 631,而不是此消息。
771. 符号 ‘Symbol’(位置)可能未初始化:在控制循环(while
、for
或 do
)的主体中初始化了给定符号,随后在循环外部使用。如果循环主体可能未被执行,则符号可能未初始化,从而导致错误。
PC-lint/FlexeLint 对表达式的分析不够强大,因此可能无法识别循环至少被执行一次。特别是在初始化数组后,情况尤为如此。请确保循环已执行,然后抑制此消息。可以全局抑制此消息(使用 -e771
)或仅针对特定符号抑制(使用 -esym
)。有时,一个简单的赋值语句就足以抑制此消息。
772. 符号 ‘Symbol’(位置)可能未初始化:将符号的地址传递给期望接收 const
指针的函数时,要求该符号已初始化。有关这种构造的风险,请参阅警告 603。有关“可能未初始化”的解释,请参阅信息性消息 771。
773. 类似表达式的宏 ‘Symbol’ 未用括号括起来:包含未加括号的二元操作符的表达式宏可能会在与其他操作符一起使用时导致意外结果。例如:
#define A B + 1
稍后在如下上下文中使用:
f( A * 2 );
将导致 B + 2
被传递给 f
,而不是 (B + 1) * 2
。建议将宏定义为:
#define A (B + 1)
最低优先级的二元操作符不会触发此消息,例如:
#define A s.x
不会触发此消息,因为这种情况通常不会引发问题。同样,未加括号的一元操作符(包括类型转换)不会生成此消息。启用可选注释 973 可以获得关于未加括号参数的更多信息。
774. 布尔表达式 ‘String’ 总是求值为[True/False]:在 if
、while
或 for
(第二个表达式)这样的上下文中,布尔表达式似乎总是求值为 True
或 False
(如消息中所述)。信息是从多个来源推断得出的,包括之前的赋值语句和初始化器。与基于常量或常量组合的消息 506 相比,此消息的生成条件不同。还可以与可选注释 944 比较,该注释有时能提供更详细的信息。
775. 非负值不可能小于零:非负量正在与 <= 0
进行比较。这有点可疑,因为非负量可以等于 0,但永远不会小于 0。非负量可能是无符号类型,或者是从无符号类型提升的,或者已经通过与不带符号位的量进行 AND
操作判断为无符号量,例如枚举类型。参见警告 568。
776. 加法可能会截断:涉及加法或减法的 int
表达式(无论是有符号的还是无符号的)被隐式或显式地转换为 long
类型。此外,long
的精度比 int
大。如果发生溢出,信息将丢失。可以将其中一个操作数强制转换为 long
类型,或将结果强制转换为某种形式的 int
。
更多关于此类错误的描述和示例,请参见警告 647。另见 790 和 942。
777. 浮点数的相等性测试:当操作符 ==
或 !=
的操作数为某种形式的浮点类型(如 float
、double
或 long double
)时,会发出此消息。由于舍入误差和浮点数表示的不精确性,测试两个浮点数的相等性是有风险的。如果你的数值算法需要进行这种测试,可以关闭此消息。若其中一个操作数可以精确表示,如 0 或 13.5,则不会触发此消息。
778. 常量表达式在操作中求值为 0:‘String’:涉及加法、减法、乘法、移位或取反的常量表达式结果为 0。这可能是有意的计算,也可能是无意的。如果是有意的,可以忽略此消息。如果其中一个操作数为 0,则可能会发出可选注释 941,而不是此消息。
779. 比较操作符中的字符串常量:Operator
:在比较操作符中发现了字符串常量。例如:
if (s == "abc") ...
这通常是一个错误。程序员是否打算使用 strcmp
?至少,这种比较在不同机器上会表现不同。如果你将字符串常量强制转换为指针类型,此消息将被忽略。
780. 空数组元素:声明的数组看起来很可疑,因为数组的元素是一个 0 维数组。例如:
extern int a[][];
extern int a[10][];
这两种情况都会触发此消息,但以下情况不会:
extern int a[][10];
在最后一种情况下,尽管缺少最外层维度,数组仍可以正确访问。
如果去掉 extern
,将会触发一个更严重的错误消息。
782. 行超过了 Integer
个字符:输入缓冲区的大小达到了内部限制。消息中包含了最大允许的字符数。这不一定意味着输入会被错误处理,额外的字符将在随后的读取中处理。然而,消息中报告的行序号可能会不准确。
783. 行没有以换行符结束:当输入行未以换行符结束或输入行中包含 NUL 字符时,会发出此消息。读取输入行时,使用 fgets
函数。通过 strlen
函数来确定读取的字符数量。如果在预期的末尾没有看到换行符,则会发出此消息。如果你的编辑器通常不会在文件的最后一行附加换行符,则可以忽略此消息。否则,检查文件中的 NUL 字符并将其删除。
784. 字符串初始化时,NUL 字符被截断:在使用字符串常量初始化数组时,没有足够的空间容纳结尾的 NUL 字符。例如:
char a[3] = "abc";
会触发此消息。这不一定是错误的,因为以这种方式初始化数组更方便。它比以下方式更简便:
char a[3] = {'a', 'b', 'c'};
另一方面,如果这是一个错误,可能会很难发现。
785. 对聚合 ‘Symbol’ 的初始化器过少:在括号初始化器中的初始化器数量少于聚合的项目数量。将使用默认初始化。一个例外情况是初始化器 {0}
,这属于不同的消息(可选注释 943)。它通常被认为是一种将所有成员初始化为 0 的方式。
786. 初始化器中的字符串拼接:虽然在初始化器中拼接字符串常量是合法的,但这通常是错误的根源。例如:
char *s[] = {"abc" "def"};
程序员是否打算创建两个字符串的数组,但忘记了逗号分隔符?或者实际上是想定义一个字符串?
787. 枚举常量 ‘Symbol’ 未在 switch
语句中使用:switch
表达式的类型为枚举类型,但至少有一个枚举常量没有出现在 case
标签中。此外,没有提供 default
案例。
788. 枚举常量 ‘Symbol’ 未在带 default
的 switch
语句中使用:switch
表达式的类型为枚举类型,但至少有一个枚举常量没有出现在 case
标签中。然而,与消息 787 不同,此处提供了 default
案例。这是消息 787 的一种温和形式。用户可能选择忽略这种形式,但保留消息 787。
789. 将自动变量 ‘Symbol’ 的地址赋值给静态变量:正在将一个自动变量的地址赋值给一个静态变量。这是危险的,因为静态变量在函数返回后仍然存在,而自动变量可能已经被销毁。这种错误很难发现。确保没有错误,并使用 -esym
抑制该变量的消息。
790. 可疑的截断操作,整数到浮点数:当涉及整数的操作(如左移和乘法)结果转换为浮点数时,可能会发生信息丢失。这种情况下会发出此消息。对于加法和减法操作,请参见可选注释 942。另见消息 647 和 776。
791. 不寻常的选项顺序:在发现临时消息抑制选项(如 !e...
)紧跟在一个常规选项后时,是否是有意的?
792. void
表达式的 void
类型强制转换:对 void
表达式进行了 void
类型强制转换。这是否是有意的?
793. 超过 ANSI 限制的 ‘String’:某些 ANSI 限制已被超出。根据 ANSI C 标准第 2.2.4.1 节,这些限制被描述为:
- 对于某些窗口系统,程序需要确保始终生成输出消息,即使没有其他消息。这种情况下,请使用选项
+e900
来确保输出。
例如,ANSI
限制外部标识符为 511。当外部标识符超过这个限制时,会触发消息 793:“外部标识符的 ANSI 限制(511 个)已超出”。尽管这种限制被超出,程序在特定编译器中仍可能正常运行。
794. 在运算符 String
的 [左/右] 操作数中可能使用了空指针 ‘Symbol’:从之前的语句中推测,可能在运算时使用了空指针(值为 0 的指针)。对于二元运算符,会使用 ‘left’ 或 ‘right’ 来标识空指针的一边,Symbol
则标识可能为空的指针变量。例如:
int *p = 0;
int i;
for(i = 0; i < n; i++)
p = &a[i];
*p = 0;
如果 for
循环的主体没有被执行,p
将保持为空。
795. 可能的除以 0 操作:在除法或取模运算中,推测除数可能为 0。
796. 通过操作符 String
访问超出数组边界的指针(超过数据的 Integer
字节):可能访问了超出边界的指针。例如:
int a[10];
int j = 100;
for(i = 0; i < n; i++)
j = n;
a[j] = 0;
这里,访问 a[j]
被标记,因为可能的情况下,for
循环不会执行,导致 j
的值为 100,这是不可接受的索引。此消息与 415 和 661 类似,但它们之间的概率不同。
797. 通过操作符 String
可能创建了超出数组边界的指针(超过数据的 Integer
字节):可能正在创建一个超出边界的指针。参见消息 415 中的参数说明以及消息 796 中关于“可行”的描述。
798. 冗余字符 ‘Char’:检测到冗余字符,可以从输入源中移除。典型示例为单独的反斜杠。
799. 数值常量 ‘Integer’ 大于无符号长整型:检测到一个比允许的无符号长整型最大值还大的整数常量。默认情况下,无符号长整型为 4 字节,但可以通过选项 -sl#
重新指定。如果允许 long long
类型(见选项 +fll
),此消息将自动抑制。参见消息 417。
801. 使用 goto
被弃用:检测到 goto
语句。大多数作者不建议使用 goto
,通常建议避免使用。虽然在少数情况下 goto
能有效使用,但通常可以通过其他方式替代。goto
会使大型函数结构变得复杂,造成混乱不堪的“意大利面条”式代码。因此,许多场合禁止使用 goto
。
802. 可能将空指针传递给函数 ‘Symbol’,上下文 Reference
:可能将一个空指针传递给名为 Symbol
的函数。问题参数通过上下文 Reference
指定。该函数要么是库函数,不接收空指针,要么是通过 -function
选项定义的用户函数。
803. 函数 ‘Symbol’ 可能导致数据溢出,参数 Integer
超过了参数 Integer
引用:当数据传输函数(如 memcpy
、strcpy
、fgets
等)中的某些参数所指定的大小可能超过缓冲区大小时,会触发此消息。此消息也可能用于通过 -function
选项指定的用户函数。
804. 函数 ‘Symbol’ 可能访问超出数组边界,参数 Integer
超过了 Integer
引用:此消息针对某些库函数(如 fwrite
、memcmp
等)发出,提示可能尝试访问超出数据范围的区域。例如,如果在 fwrite
调用中指定的数据长度可能超过可用的数据大小。函数名为 Symbol
,参数通过参数编号指定。
805. 预期使用 L"..."
来初始化宽字符字符串:初始化宽字符数组或指针时,未使用前缀 L
。例如:
wchar_t a[] = "abc";
预期的做法应该是:
wchar_t a[] = L"abc";
806. 小位域是有符号的而不是无符号的:发现小位域(小于 int
的位宽)使用了有符号类型。由于最重要的位是符号位,这种做法可能导致意外结果。例如:
struct { int b:1; } s;
s.b = 1;
if( s.b > 0 ) /* 应该成功但实际上失败 */
...
807. 可能传递负值(Integer
)给函数 ‘Symbol’,上下文 Reference
:推测传递给函数 Symbol
的一个整数参数可能为负数,但函数预期的是正数。消息包含了函数名(Symbol
)、可疑的整数值(Integer
)以及参数编号(上下文 Reference
)。该函数可能是一个标准库函数,如 malloc
或 memcpy
,或通过 -function
或 -sem
选项定义的用户函数。有关示例和进一步解释,请参阅消息 422。
808. 符号 ‘Symbol’ 缺少显式类型,假定为 int
:在声明中缺少显式类型。与警告 601 不同,此声明可能伴随着存储类或修饰符(限定符)或两者。例如:
extern f(void);
会触发消息 808。如果不存在 extern
,则会触发消息 601。
即使像 unsigned
、signed
、short
和 long
这样的关键词隐式假定为 int
的基础类型,也被视为显式类型说明符。
809. 可能返回自动变量的地址通过变量 ‘Symbol’:指针变量可能保存了自动变量的地址。通常返回栈上项的地址是不正确的,因为返回的函数所分配的栈部分可能在返回后被覆盖。
810. 修改了保管指针 ‘Symbol’ 的算术运算:保管变量是直接接收 malloc
或 new
调用结果的变量。修改这样的变量是不合适的,因为它必须最终被 free
或 delete
。应首先复制保管指针,然后修改副本。副本被称为别名。
811. 可能释放了指针别名:对一个指针执行了 free
或 delete
操作,而该指针似乎不是所分配存储的保管变量。请参阅消息 810 了解“保管变量”的定义。删除别名指针是危险的,因为这可能导致同一存储区域被多次删除,从而引发不确定的行为。始终试图通过保管指针释放存储空间,仅修改别名指针。
812. 静态变量 ‘Symbol’ 的大小为 Integer
:静态符号所占用的存储空间达到了或超过了通过 -size
选项指定的大小。
813. 函数 ‘Symbol’ 中的自动变量 ‘Symbol’ 的大小为 Integer
:自动符号所占用的存储空间达到了或超过了通过 -size
选项指定的大小。
814. 无用的声明:声明了一个没有标记的 struct
,但未声明变量。例如:
struct { int n; };
这样的声明不能很好地被使用。
815. 修改了未保存的指针:分配表达式(如 malloc
、calloc
、new
)未立即分配给变量,而是用于其他表达式的操作数。这将使释放已分配的存储空间变得困难。例如:
p = new X[n] + 2;
将触发此消息。更推荐的方式是:
q = new X[n];
p = q + 2;
这样可以通过保管指针 q
释放存储空间。
另一种会触发此消息的示例是:
p = new (char *)[n];
这是程序员的严重错误。这并不会分配指针数组,如新手所想。它被解析为:
p = (new (char *))[n];
它实际上分配了一个指针,并尝试对这个“数组”进行索引。
816. 非 ANSI 格式规范:在格式处理函数(如 printf
或 scanf
)中发现了一个非标准的格式说明符。该说明符可能是特定编译器支持的扩展或事实上的标准,但不是 ANSI 标准的一部分。
817. 操作符 String
中使用的下标 Integer
可能为负值:一个推测为负值的整数被添加到数组指针或分配区域(通过 malloc
、new
等分配)指针中。此消息不针对来源不明的指针,因为在一般情况下负下标是合法的。
这个操作可以出现在下标操作中,也可以作为指针算术的一部分。操作符由 String
标识,整数值为 Integer
。
818. 参数指针 ‘Symbol’(位置)可以声明为指向 const
:例如:
int f(int *p) { return *p; }
可以重新声明为:
int f(const int *p) { return *p; }
将参数声明为指向 const
的指针有其优势,特别是可以将 const
数据项的地址传递给此类参数。此外,它还可以提供更好的文档说明。
有关可以向声明中添加 const
的其他情况,请参见消息 952、953、954 和 1764。
820. 布尔测试中的赋值已加括号:在布尔测试中对赋值操作进行了测试,而且赋值操作已加了括号。例如:
if ((a = b)) // Info 820
此类表达式会触发消息,而未加括号的情况则会触发消息 720,例如:
if (a = b) // Info 720
(即没有外部括号的情况)会触发消息 720。我们当然不会计算 if
子句中需要的外部括号。
之所以对这些消息进行区分,是为了允许程序员采用某些编译器(特别是 gcc
)所建议的习惯,即对要测试的赋值操作总是使用一组冗余的括号。在这种情况下,你可以使用 -e820
抑制消息 820,同时仍保留消息 720 的启用。
821. 赋值右侧未加括号:发现了以下形式的赋值操作符:
a = b || c;
a = b && c;
a = b ? c : d;
此外,赋值出现在需要获得值的上下文中。例如:
f(a = b ? c : d);
这样的代码可能会让读者将赋值误认为是相等性测试。为避免任何疑惑,建议为右侧的赋值加上括号,如:
f(a = (b ? c : d));
825. 流程进入 case/default
,未加 fallthrough
注释:一个常见的编程错误是忘记在 switch
语句的 case
之间添加 break
语句。例如:
case 'a': a = 0;
case 'b': a++;
这里的流程从 case 'a'
进入 case 'b'
,是否是有意的?为了表示这种行为是故意的,可以在注释中使用 -fallthrough
选项,如:
case 'a': a = 0;
//lint -fallthrough
case 'b': a++;
此消息与警告 616(“流程进入 case/default
”)类似,提供了更严格的检测机制。警告 616 通过插入任意注释就可以抑制,因此遗漏 break
可能会被未检测到的注释所掩盖。这对于写有详细注释的程序可能带来危险。
826. 可疑的指针到指针转换(区域太小):指针被隐式或显式地转换为另一个指针,目标指针指向的区域比源指针的区域大。例如:
long *f(char *p) { return (long *)p; }
827. 循环无法到达:发现一个循环结构(for
、while
或 do
)无法到达。是否是疏忽?可能程序员计划通过标签跳转进入循环中间部分。正因为如此,我们将此消息归为信息性消息,而不是通常针对不可达语句发出的警告 527。但需要注意,跳转进入循环在任何情况下都是一种可疑的实践。
828. 函数 ‘Name’ 的语义被复制到函数 ‘Name’:一个带有内建语义或用户定义语义的函数被 #define
重定义为某个类似的函数名,该名字通过在前面或后面添加下划线构成。例如:
#define strcmp(a,b) __strcmp__(a,b)
这种情况将触发消息 828。正如消息所述,语义将自动转移到新函数。
829. 字符串拼接中的空字符串:在字符串拼接中发现了一个空字符串。例如:
char *s = "abc" "";
这种拼接是合法的,但它通常不会增加任何内容。你可能打算删除空字符串。
830. 重复的测试 ‘String’:在同一表达式中对相同的条件进行了多次测试。例如:
if (a == 0 && a == 0)
这种情况通常是由于代码编写疏忽导致的,应该进行修正。如果是有意为之,可以通过插入注释来说明目的,并使用 -e830
抑制此消息。
831. 重复的控制流语句:同一个控制流语句(如 break
、continue
、goto
)在同一个位置出现了多次。例如:
for (i = 0; i < n; i++) {
if (i == 5) break;
break;
}
第一个 break
后的第二个 break
是多余的。应移除多余的控制流语句。
832. 函数 ‘Name’ 的内建语义未定义:某个内建函数通过 #define
被重定义了,且其重定义为一个非内建函数名,导致函数的内建语义丢失。例如:
#define memcpy my_memcpy
这种重定义会丢失 memcpy
的内建优化。如果你不希望这种优化,应该抑制此消息。
834. 左值作为操作数没有任何效果:一个表达式使用了左值,但没有对其进行任何操作。例如:
x;
这段代码没有产生任何效果,可能是代码编写时遗漏了某些操作。请检查代码并确认是否缺少某些逻辑。
835. 传递了大对象 ‘Type’ 的实例:发现通过值传递了一个大对象。尽量使用指针传递或引用传递以减少开销。例如:
void f(struct LargeObject obj);
应改为:
void f(const struct LargeObject *obj);
836. 构造没有意义:比较相同的值:对同一值进行了比较。例如:
if (a == a)
这种比较是无效的,应该移除。
837. 布尔值的否定总是为真:在表达式中出现了双重否定,例如:
if (!!a)
这实际上不会改变原有布尔值的含义,因此是多余的。应将其简化为:
if (a)
838. 用于表达式的 volatile
变量未被读取:在表达式中使用了 volatile
变量,但其值未被读取。例如:
volatile int v;
v = 5;
这不会产生预期效果,应该确保变量的值被正确读取或使用。
839. 静态变量的地址被返回:在函数中返回了一个静态变量的地址。虽然静态变量的生命周期比自动变量长,但返回它的地址可能在某些情况下并不是最佳做法,特别是在并发访问的情况下。请确保这是你的预期行为。
840. 内存泄漏:动态分配的内存未被释放:动态分配了内存(如通过 malloc
、calloc
或 new
),但该内存未被释放。例如:
int *p = malloc(sizeof(int) * 10);
如果没有调用 free(p)
,则会触发此消息。请确保在不再需要时释放动态分配的内存,以避免内存泄漏。
841. 使用了 goto
语句跳入代码块:goto
语句用于跳入代码块,这种做法通常不推荐,因为它会导致程序流混乱。例如:
goto label;
...
label:
尽量避免使用 goto
,并考虑使用更结构化的控制流语句(如 break
、continue
或函数调用)。
842. 内存分配失败未处理:在进行动态内存分配时(如通过 malloc
或 new
),没有检查返回值是否为 NULL
。例如:
p = malloc(100);
如果 malloc
失败,返回 NULL
,但此处没有任何检查会导致潜在的崩溃或错误。正确的做法是:
p = malloc(100);
if (p == NULL) {
// 错误处理
}
843. 指向局部变量的指针返回:返回了一个指向局部变量的指针。例如:
int *f() {
int x = 10;
return &x;
}
在函数返回后,局部变量的作用域结束,指针将指向无效地址,这可能导致未定义行为。局部变量不应返回其地址。
844. 整数乘法可能导致溢出:在两个整数类型进行乘法操作时,结果可能超出整数类型的表示范围,导致溢出。例如:
int result = a * b;
如果 a
和 b
都是大值,result
可能溢出。应考虑将其中一个操作数转换为更大范围的类型,例如 long
。
845. 在指针运算中,指针可能为 NULL
:在指针运算(如解引用、算术运算)中,指针可能为 NULL
。例如:
*p = 5;
如果 p
为 NULL
,则会发生崩溃。应在操作前检查指针是否为 NULL
:
if (p != NULL) {
*p = 5;
}
846. 类型转换可能丢失精度:在进行类型转换时,目标类型的范围小于源类型,可能会导致数据丢失。例如:
int i = 100000;
short s = (short)i;
由于 short
的范围小于 int
,可能会丢失高位数据。应谨慎使用类型转换,并确保数据不会丢失。
847. 函数 ‘Name’ 的返回值未使用:函数的返回值未被使用。例如:
scanf("%d", &x);
此处未检查 scanf
的返回值,忽略了读取输入的成功与否。应检查返回值以确保操作成功。
848. 在循环中可能导致死锁的锁操作:在循环中使用了锁机制(如 mutex
),但没有合理的解锁机制,这可能会导致死锁。例如:
while (condition) {
lock();
// 没有解锁
}
确保在循环退出时有解锁机制,避免死锁。
849. 变量 ‘Symbol’ 未在所有分支中初始化:变量在某些分支中未被初始化,可能导致在未定义的情况下使用该变量。例如:
if (condition) {
x = 10;
}
// 其他分支没有对 x 进行初始化
应确保在所有逻辑分支中对变量进行初始化。
850. 静态分析发现可能的内存泄漏:发现程序可能存在内存泄漏的风险,通常是由于没有对动态分配的内存进行正确的释放。例如:
p = malloc(100);
// 没有 free(p)
建议检查所有内存分配并确保在适当的时候释放。
851. 静态变量 ‘Symbol’ 使用时未加锁:静态变量通常在多线程环境下共享,如果未加锁访问,可能导致数据竞争和未定义行为。例如:
static int counter = 0;
counter++;
应使用适当的锁机制保护静态变量的访问:
lock();
counter++;
unlock();
852. 局部静态变量的指针被返回:返回了一个指向局部静态变量的指针。这可能在某些环境下是允许的,但需谨慎处理。例如:
static int x;
return &x;
局部静态变量的生命周期超出了函数作用域,可以安全返回指针,但在多线程环境下使用时需要加锁。
853. 循环变量的范围可能导致意外行为:循环变量的范围可能导致意外行为,尤其是在与数组等其他数据结构交互时。例如:
for (i = 0; i <= n; i++) {
array[i] = 0;
}
当 i
等于 n
时,可能会超出数组的边界,应确保循环范围与数据结构的大小相匹配。
854. 在锁定区域内的阻塞调用可能导致死锁:在持有锁的情况下进行了可能阻塞的操作(例如 I/O 操作),这可能导致死锁。例如:
lock();
read(file, buffer, size);
unlock();
如果 read
操作被阻塞,其他线程将无法获得锁,可能会导致系统停滞。应避免在锁定区域内执行阻塞操作,或者确保不会导致死锁。
855. 使用了过时的库函数 ‘Symbol’:检测到使用了已被认为过时或不推荐使用的标准库函数。例如,使用 gets
函数读取字符串可能导致缓冲区溢出,因为它不会检查输入的长度。建议使用更安全的替代函数(如 fgets
)。
856. 在 switch
语句中漏掉了枚举值 ‘EnumValue’:在 switch
语句中,未处理某些枚举值。例如:
enum Color { RED, GREEN, BLUE };
Color c = RED;
switch (c) {
case GREEN: ... break;
case BLUE: ... break;
}
RED
的情况未被处理,可能导致意外行为。建议在 switch
中处理所有枚举值,或者添加一个 default
分支以处理未覆盖的情况。
857. 检测到了不安全的类型转换:在类型转换时,源类型和目标类型的存储空间差异较大,可能导致数据截断或扩展。例如:
long x = 1000000;
short y = (short)x;
这种转换可能导致数据丢失,建议避免直接转换不同类型的变量,特别是在有符号和无符号类型之间进行转换时。
858. 对齐要求未得到满足:在指针运算或内存访问中,未满足目标架构的对齐要求。例如:
char *p = buffer;
int *i = (int*)p;
如果 buffer
没有正确对齐,可能会导致未定义行为,特别是在某些架构上(例如 RISC 处理器)会引发硬件异常。建议使用适当的内存对齐方式,例如通过 aligned_malloc
或其他对齐分配方法。
859. 函数指针转换未定义行为:将一个函数指针转换为不同类型的函数指针时,可能会导致未定义行为。例如:
void (*fptr)(int);
fptr = (void(*)(void))another_function;
这种操作未在标准中明确规定,因此在不同的编译器或架构中表现可能不同。建议保持函数指针的类型一致性,避免不必要的转换。
860. 潜在的堆栈溢出风险:在局部变量或数组声明中使用了大量内存,可能导致堆栈溢出。例如:
int large_array[1000000];
如果堆栈空间不足,程序可能会崩溃。建议将大型数据结构分配到堆中,例如使用 malloc
或 new
动态分配内存。
861. 字符串操作可能未终止:在进行字符串操作时,未确保字符串的结尾有终止符 \0
。例如:
char str[10];
strcpy(str, "long_string");
此操作可能导致缓冲区溢出,因为目标缓冲区的大小不足以容纳源字符串及其终止符。应使用安全的字符串操作函数,如 strncpy
,并确保适当地处理终止符。
862. 使用未初始化的指针:在使用指针之前,未对其进行初始化。例如:
int *p;
*p = 10; // 未初始化的指针
使用未初始化的指针会导致未定义行为,可能会崩溃或数据损坏。应始终初始化指针,在分配内存后再使用。
863. 在同一表达式中多次修改同一变量:在一个表达式中多次修改了同一个变量,这可能导致未定义行为。例如:
i = i++ + ++i;
这种代码在不同编译器上可能会有不同的表现,建议避免在同一表达式中多次修改同一变量。应将修改操作分开,以确保行为明确。
864. 不安全的 memcpy
操作,目标区域可能与源区域重叠:在使用 memcpy
进行内存拷贝时,目标区域和源区域可能重叠。根据标准,memcpy
不允许区域重叠,这会导致未定义行为。应使用 memmove
来处理可能重叠的内存区域。
865. 不安全的字符操作,未处理结尾的空字符:在处理字符串时,未考虑到结尾的空字符(NUL 字符),可能导致缓冲区溢出或数据损坏。例如:
char buffer[10];
strncpy(buffer, "Hello, World!", 10);
这种操作没有在最后一个字符处添加终止符 \0
,建议确保字符串的正确终止,或者使用更安全的字符串操作函数。
866. switch
语句中未处理的值超出范围:在 switch
语句中处理了一些值,但未处理所有可能的值,且未提供 default
处理。例如:
switch (value) {
case 1:
break;
case 2:
break;
}
如果 value
超出了 1
和 2
,将会出现未定义行为。建议添加一个 default
处理分支,以处理所有未列出的情况。
867. 超过指定的警告级别:当前警告的级别超出了指定的警告级别设置。此消息用于提示开发者,他们可能设置了更严格的警告级别,导致编译器报告更多信息性或低级别的警告。
868. 指针与非指针类型之间的转换:尝试将指针类型与非指针类型之间进行转换,这是不安全的做法。例如:
int a;
int *p = (int *)a;
这种转换可能会导致指针失效或未定义行为,建议避免这种不兼容的类型转换。
869. 数组下标可能超出界限:在使用数组时,所使用的下标可能超出了数组的边界,导致未定义行为或潜在的崩溃。例如:
int array[10];
array[10] = 0; // 超出界限
建议确保数组下标在合法范围内,或者使用更安全的数组操作方式。
870. 递归调用可能导致堆栈溢出:检测到递归调用,且递归深度可能导致堆栈溢出。例如:
void recurse() {
recurse();
}
递归调用过深会占用大量堆栈空间,建议限制递归深度或改用迭代方法。
871. 用不同类型的指针解引用相同的内存区域:对同一内存区域使用了不同类型的指针进行解引用,可能导致未定义行为或数据不一致。例如:
int *p = malloc(sizeof(int));
char *q = (char *)p;
*q = 'a';
不同类型的指针可能有不同的对齐要求,应避免这种做法,或者确保内存的对齐和类型一致。
872. 动态分配内存后未使用:分配了动态内存,但从未使用该内存。例如:
p = malloc(100);
// 没有使用 p
这会导致内存浪费,建议确保分配的内存被正确使用,或者如果不需要,则释放该内存。
873. inline
函数可能无法被内联:虽然使用了 inline
关键字,编译器可能不会实际内联该函数。例如函数过于复杂或占用太多代码空间,导致编译器决定不内联。建议简化函数或查看编译器的优化报告。
874. 使用了未初始化的局部静态变量:局部静态变量可能未初始化。这种变量在函数多次调用时保持其值,但如果没有正确初始化,可能会导致意外行为。
875. 忽略了位运算中的符号位:在位运算中忽略了符号位,可能导致错误的结果。例如:
int a = -1;
a = a >> 1;
右移操作未考虑符号位可能导致错误的结果,建议使用无符号类型进行位运算。
876. 位移值超出类型的位数:位移操作的位数超过了数据类型的位宽。例如:
int a = 1;
a = a << 32;
这会导致未定义行为。建议确保位移操作的位数不超过数据类型的位宽。
877. 未正确处理 I/O 操作的返回值:在进行 I/O 操作时,未检查操作的返回值。例如:
fwrite(buffer, size, count, file);
如果 fwrite
失败,返回值会小于期望值,但此处未进行任何检查。建议检查 I/O 操作的返回值,以确保操作成功。
878. 可能的空指针访问:在某些代码路径中,指针可能未被初始化或被赋值为 NULL
,而后续代码尝试解引用该指针。例如:
int *p = NULL;
*p = 10; // 未检查 p 是否为 NULL
在使用指针之前,应该确保指针已被正确初始化,避免空指针访问。
879. 未能正确释放资源:检测到在程序执行过程中分配的资源(如文件句柄、内存或其他系统资源)未被正确释放,可能会导致资源泄漏。例如:
FILE *f = fopen("file.txt", "r");
// 没有 fclose(f)
在资源不再需要时,应该及时释放它们,以避免资源泄漏问题。
880. 位操作中的符号扩展问题:当对有符号类型执行位操作时,符号位可能会影响结果。例如:
int a = -1;
a = a >> 1;
由于符号扩展的存在,右移操作可能不会产生预期结果。建议使用无符号类型进行位操作,以确保结果正确。
881. 函数参数未使用:某些函数参数在函数体中未被使用,可能是由于设计上的遗漏或错误。例如:
void foo(int a) {
// a 未被使用
}
如果参数不需要使用,可以考虑将其删除或标记为未使用。若是有意保留,可以在注释中说明。
882. 局部变量未被使用:在函数中声明了某个局部变量,但未对其进行任何使用。例如:
int unused = 0;
未使用的局部变量会增加代码的复杂度,建议删除无用变量,保持代码简洁。
883. 不匹配的类型传递给库函数:传递给标准库函数的参数类型与预期不匹配,可能导致未定义行为。例如:
char *str;
scanf("%d", &str);
此处 scanf
预期的参数类型应为 int*
,但传递了 char*
,应确保传递的参数类型与库函数的要求匹配。
884. 递归调用未适当限制:递归调用可能缺乏适当的终止条件,导致无限递归,从而可能引发堆栈溢出。例如:
void recurse() {
recurse(); // 没有终止条件
}
应确保递归函数有合适的终止条件,防止过深的递归调用导致程序崩溃。
885. 文件操作失败未处理:文件打开、读取或写入操作失败后未处理错误条件。例如:
FILE *f = fopen("file.txt", "r");
// 没有检查 fopen 的返回值
文件操作可能失败(如文件不存在或权限不足),应检查文件操作函数的返回值,并进行适当的错误处理。
886. 非法的内存访问:对无效的内存地址进行了访问,可能导致程序崩溃。例如:
int *p = NULL;
*p = 10; // 对 NULL 指针的非法访问
在进行指针操作前,应确保指针指向有效的内存地址。
887. 超过标准的位数操作:位操作符的使用超过了标准位数的限制。例如,在 32 位系统上,左移或右移超过 32 位可能会导致未定义行为。应确保位移量在合理范围内。
888. 浮点数除以零:检测到浮点类型的除法操作中,除数为零。例如:
float a = 1.0;
float b = 0.0;
float result = a / b;
浮点数除以零会导致未定义行为或返回 Inf
,应在除法操作前检查除数是否为零。
889. 通过无效指针访问动态内存:尝试通过无效指针(如已经释放的指针)访问动态内存。例如:
free(p);
*p = 10; // 访问已释放的内存
在释放内存后,指针应被设置为 NULL
,以避免后续的非法访问。
890. 错误的内存分配大小:在调用 malloc
或 calloc
时,传递了错误的大小,可能导致内存分配不足或过多。例如:
int *p = malloc(10); // 应为 malloc(10 * sizeof(int))
应确保传递给内存分配函数的大小是正确的,通常使用 sizeof
操作符来计算所需内存大小。