文章目录
- Wconversion
- Werror
- CMakeList使用这些编译选项
- Wno-error
- Wunused
- Wall,Wextra
- Wuninitialzed
- Weffc++
- Debug模式与Release模式区别
- Wdelete-non-virtual-dtor
- Wzero-as-null-pointer-constant
- Wparentheses
- Wempty-body
- Wsign-compare和Wsign-conversion
- Wformat
- Wold-style-cast
- Wloop-range-construct(gcc不支持,clang支持)
- Wstrict-aliasing(gcc不支持,clang支持)
- Wrestrict
- Winvalid-memory-model
- Wsuggest-override
- Wconversion
- Wattributes
- 头文件就地定义一个函数,附在头里
- Wmissing-declarations
- 就地CC源文件定义函数
- Warray-bounds
- Wdangling-pointer=2(gcc无法检测出来)
- Wmismatched-new-delete
- Wvla :variable-length-array
- Wreorder,关闭的话Wno-reorder
- 析构函数默认不抛出异常的,除非显示加上noexcept
- Werror=return-type
- Wswitch(gcc无法检测出来)
- -Wimplicit-fallthrough(gcc检测不出来)
- Wshadow
- 参考
Wconversion
用于检测潜在的类型转换问题;
它主要关注以下两种类型的转换:
-
窄化转换(Narrowing Conversion)
当一个较大的数据类型被赋值给较小的数据类型时,可能会导致数据丢失。例如:
将 double 转换为 int。
将 long 转换为 short。 -
精度损失的浮点数转换
当浮点数之间的转换可能导致精度损失时,也会触发警告。例如:
将 double 转换为 float。
#include <iostream>
int main() {
int a = 300;
char b = a; // 隐式将 int 转换为 char,可能导致数据丢失
std::cout << static_cast<int>(b) << std::endl;
return 0;
}
编译
Wfloat-conversion
-Wfloat-conversion 是 GCC 编译器中的一个警告选项,用于检测浮点数与整数之间的隐式转换问题
-Wfloat-conversion 是 -Wconversion 的子集,专门针对以下两种情况发出警告:
(1)从浮点数到整数的转换
当浮点数(如 float 或 double)被隐式转换为整数类型(如 int)时,小数部分会被直接截断,导致精度丢失。例如:
double d = 3.99;
int i = d; // 小数部分被截断,i 的值为 3
(2)从整数到浮点数的转换
当整数类型被隐式转换为浮点数类型时,如果浮点数无法精确表示该整数值,则会引发精度损失。例如:
long l = 1234567890123456789L;
float f = l; // float 精度不足,无法准确表示该值
Werror
用于将所有警告(warnings)提升为错误(errors)
#include <iostream>
int main() {
int a = 300;
char b = a; // 隐式将 int 转换为 char,可能导致数据丢失
std::cout << static_cast<int>(b) << std::endl;
return 0;
}
CMakeList使用这些编译选项
add_compile_options(
-Werror
-Wconversion
-Wunused
-Wall
-Wextra
-Wuninitialized
-Weffc++
-Wno-non-virtual-dtor
-Wdelete-non-virtual-dtor
-Wzero-as-null-pointer-constant
-Wparentheses
-Wempty-body
-Wsign-compare
-Wsign-conversion
-Wold-style-cast
-Wrange-loop-construct
-Wformat
-Wstrict-aliasing=3
-Wrestrict
-Winvalid-memory-model
-Wsuggest-override
-Wattributes
-Wmissing-declarations
-Warray-bounds
-Wdangling-pointer=2
-Wreturn-local-addr
-Wuse-after-free
-Wmismatched-new-delete
-Wvla
-Walloca
-Wreorder
-Woverflow
-Werror=return-type
-Wswitch
-Wimplicit-fallthrough
# -Wshadow=local
# -Werror
# -Wall
# -Wextra
# -Weffc++
# -Wunused
# -Wunused-but-set-variable
# -Wunused-but-set-parameter
# -Wunused-const-variable
# -Wunused-value
# -Werror=unused-result
# -Werror=use-after-free=3
# -Werror=string-compare
# -Werror=return-type
# -Werror=deprecated-copy-dtor
# -Werror=deprecated-copy
# -Wnon-virtual-dtor
# -Werror=delete-non-virtual-dtor
# -Werror=mismatched-new-delete
# -Werror=suggest-override
# -Werror=conversion-null
# -Werror=zero-as-null-pointer-constant
# -Werror=reorder
# -Werror=class-memaccess
# -Wparentheses
# -Wconversion
# -Werror=float-conversion
# -Wenum-conversion
# -Wsign-conversion
# -Wsign-compare
# -Wsign-promo
# -Wold-style-cast
# -Wrange-loop-construct
# -Werror=uninitialized
# -Werror=maybe-uninitialized
# -Wmissing-field-initializers
# -Wmissing-declarations
# -Wempty-body
# -Wlogical-op
# -Wlogical-not-parentheses
# -Werror=address
# -Werror=multichar
# -Werror=overflow
# -Werror=restrict
# -Werror=strict-aliasing
# -Werror=invalid-memory-model
# -Werror=vla
# -Wattributes
# -Wstrict-overflow
# -Werror=stringop-overflow
# -Werror=shift-overflow
)
Wno-error
它通常与 -Werror 配合使用,允许开发者将某些警告从“错误”降级为普通的“警告”。
当使用 -Werror 时,所有警告都会被提升为错误,导致编译失败。然而,在某些情况下,你可能希望对某些警告保持宽容,而不影响整个项目的编译流程。这时可以使用 -Wno-error 来指定哪些警告不应被视为错误。
-Wno-error=<warning-flag>
- <warning-flag>:指代具体的警告类型(例如 float-conversion、sign-compare 等)。
- 这个选项会禁用指定警告的 -Werror 行为,但仍会发出普通警告。
#include <iostream>
int main() {
int a = 300;
char b = a; // 隐式将 int 转换为 char,可能导致数据丢失
std::cout << static_cast<int>(b) << std::endl;
return 0;
}
Wunused
Wunused 是一组与“未使用”相关的警告选项的集合,用于检测以下几种常见问题:
- 未使用的变量
- 声明了变量但从未使用它。
- 未使用的函数
- 定义了函数但从未调用它。
- 未使用的函数参数
- 函数定义中声明了参数,但在函数体内没有使用该参数。
- 未使用的标签
- 在代码中标记了标签(如 goto 标签),但从未引用它。
- 未使用的返回值
- 调用了函数但忽略了其返回值(需要启用额外选项)。
int main() {
int x = 42; // 未使用
return 0;
}
优雅解决Wunused
#include <cstdio> // 修正头文件名称
struct C {
int x; // 成员变量
C([[maybe_unused]] int i) {}
void func() { // 修正函数定义
printf("C::func %d\n", x); // 修正格式化字符串和参数
}
};
int main() {
C c(3); // 修正对象初始化
c.func(); // 调用成员函数
return 0; // 返回值
}
#include <cstdio> // 修正头文件名称
struct C {
int x; // 成员变量
C(int ) {}
void func() { // 修正函数定义
printf("C::func %d\n", x); // 修正格式化字符串和参数
}
};
int main() {
C c(3); // 修正对象初始化
c.func(); // 调用成员函数
return 0; // 返回值
}
Wunused,函数加上[[nodiscard]]说明返回值一般都有用
#include <iostream>
[[nodiscard]] double square(double x) {
return x * x; // 修正拼写错误 "raturn" -> "return"
}
int main() {
double x = square(1.23); // 修正变量名和符号错误
x = square(-1.254);
// std::cout << "Result: " << x << std::endl; // 添加输出语句
return 0; // 修正返回值
}
解决[[nodiscard]]
#include <iostream>
[[nodiscard]] double square(double x) {
return x * x; // 修正拼写错误,添加返回值
}
int main() {
[[maybe_unused]] double value = square(1.234); // 正确使用属性
return 0; // 修正返回值
}
#include <iostream>
[[nodiscard]] double square(double x) {
return x * x; // 修正拼写错误 "raturn" -> "return"
}
int main() {
(void)square(1.234); // 修正函数调用和语法错误
return 0; // 修正返回值 "B" -> "0"
}
Wall,Wextra
推荐组合 :在开发阶段建议同时启用 -Wall 和 -Wextra
特性 | -Wall | -Wextra |
覆盖范围 | 常见警告 | 更多的额外警告 |
未使用的参数 | 不检测 | 检测 |
符号比较 | 不检测 | 检测 |
空循环体 | 不检测 | 检测 |
重复条件分支 | 不检测 | 检测 |
适用场景 | 日常开发 | 高质量代码审查 |
Wuninitialzed
在开发过程中,建议始终启用 -Wall 和 -Wextra,它们已经包含了 -Wuninitialized。
gcc开启优化和关闭优化的不同
gcc开启优化有时候报错会更多,建议编译时开启优化,没有优化,下面不报错,开启优化,func就内联了会报错,变量没有初始化
#include <cstdio> // 包含 printf 所需的头文件
struct C {
int x; // 修正变量声明错误
void func() { // 修正函数定义
printf("C::func %d\n", x); // 修正格式化字符串和参数
}
};
int main() {
C c; // 修正对象初始化
c.func(); // 调用成员函数
return 0; // 修正返回值
}
开启优化
所以需要初始化
#include <cstdio> // 包含 printf 所需的头文件
struct C {
int x; // 修正变量声明错误
C(int i) : x(i) {} // 修正构造函数定义和初始化列表
void func() { // 修正函数定义
printf("C::func %d\n", x); // 修正格式化字符串和参数
}
};
int main() {
C c(42); // 修正对象初始化,使用默认构造函数
c.func(); // 调用成员函数
return 0; // 修正返回值
}
Weffc++
gcc的所有编译选项都可以从man gcc找到
开启effecc++最佳实践编译选项Weffec++,这个最佳实践比较晚,低于cpp11。
#include <iostream>
struct C {
int *p; // 指向整数的指针
// 默认构造函数
C() : p(new int(0)) {}
// 拷贝构造函数,实现深拷贝
C(const C &that) : p(new int(*that.p)) {}
// 析构函数,释放动态分配的内存
~C() { delete p; }
};
int main() {
C c1; // 默认构造
C c2 = c1; // 触发拷贝构造
return 0;
}
没有实现拷贝赋值也有告警
#include <iostream>
struct C {
int *p; // 指向动态分配的整数
// 默认构造函数
C() : p(new int(0)) {
std::cout << "Default constructor called\n";
}
// 拷贝构造函数(深拷贝)
C(const C &that) : p(new int(*that.p)) {
std::cout << "Copy constructor called\n";
}
// 赋值运算符重载(深拷贝 + 自赋值检测)
C& operator=(const C &that) {
if (this != &that) { // 避免自赋值
*p = *that.p;
}
std::cout << "Assignment operator called\n";
return *this;
}
// 析构函数(释放内存)
~C() {
std::cout << "Destructor called\n";
delete p;
}
};
int main() {
C c1; // 调用默认构造函数
C c2 = c1; // 触发拷贝构造
C c3; // 默认构造
c3 = c1; // 触发赋值运算符重载
return 0;
}
#include <iostream>
struct C {
int *p; // 指向动态分配的整数
// 默认构造函数
C() : p(new int(0)) {
std::cout << "Constructor called\n";
}
// 析构函数,释放动态分配的内存
~C() {
std::cout << "Destructor called\n";
delete p;
}
};
int main() {
C c1; // 创建对象,调用构造函数
return 0;
}
Debug模式与Release模式区别
Debug模式
g++ -g -O0 -DDEBUG main.cpp -o main_debug
- 包含调试信息(使用 -g 选项)。
- 禁用优化(使用 -O0,保证代码逐行执行的可预测性)。
#include <iostream>
struct C {
int *p; // 指向动态分配的整数
// 默认构造函数
C() : p(new int(0)) {
std::cout << "Constructor called\n";
}
// 析构函数,释放动态分配的内存
~C() {
std::cout << "Destructor called\n";
delete p;
}
};
int main() {
C c1; // 创建对象,调用构造函数
C c2 =c1;
return 0;
}
debug模式报错,debug模式出现double free错误
Release模式
g++ -O2 -DNDEBUG main.cpp -o main_release
- 开启优化(-O2 或 -O3),提高执行速度。
- 不包含调试信息(无 -g 选项)。
#include <iostream>
struct C {
int *p; // 指向动态分配的整数
// 默认构造函数
C() : p(new int(0)) {
std::cout << "Constructor called\n";
}
// 析构函数,释放动态分配的内存
~C() {
std::cout << "Destructor called\n";
delete p;
}
};
int main() {
C c1; // 创建对象,调用构造函数
C c2 =c1;
return 0;
}
Wdelete-non-virtual-dtor
基类的析构函数定义为virtual的原因
#include <iostream>
using namespace std;
struct Animal {
virtual void func() {
cout << "Animal!" << endl;
}
//如果不定义为virtual,下面的case子类的析构函数没有被调用
virtual ~Animal() {
cout << "Animal Destructor!" << endl;
}
};
struct Cat : public Animal {
void func() override {
cout << "Cat Meow!" << endl;
}
virtual ~Cat() {
cout << "Cat Destructor!" << endl;
}
};
struct Dog : public Animal {
void func() override {
cout << "Dog Bark!" << endl;
}
virtual ~Dog() {
cout << "Dog Destructor!" << endl;
}
};
void foo(Animal* a) {
a->func(); // 调用 Animal 的派生类函数
delete a; // 删除对象,触发析构函数
}
int main() {
Animal* a = new Cat(); // 创建 Cat 类型对象
foo(a); // 传入 Animal 指针,调用 foo
// a = new Dog(); // 创建 Dog 类型对象
// foo(a); // 传入 Animal 指针,调用 foo
return 0;
}
如果基类的虚析构函数不是virtual,如果实例化子类对象没啥问题
#include <iostream>
using namespace std;
struct Animal {
virtual void func() {
cout << "Animal!" << endl;
}
~Animal() {
cout << "Animal Destructor!" << endl;
}
};
struct Cat : public Animal {
void func() override {
cout << "Cat Meow!" << endl;
}
virtual ~Cat() {
cout << "Cat Destructor!" << endl;
}
};
struct Dog : public Animal {
void func() override {
cout << "Dog Bark!" << endl;
}
virtual ~Dog() {
cout << "Dog Destructor!" << endl;
}
};
void foo(Animal* a) {
a->func(); // 调用 Animal 的派生类函数
delete a; // 删除对象,触发析构函数
}
int main() {
// Animal* a = new Cat(); // 创建 Cat 类型对象
// foo(a); // 传入 Animal 指针,调用 foo
Cat c;
c.func();
// a = new Dog(); // 创建 Dog 类型对象
// foo(a); // 传入 Animal 指针,调用 foo
return 0;
}
Wdelete-non-virtual-dtor
#include <iostream>
using namespace std;
struct Animal {
virtual void func() {
cout << "Animal!" << endl;
}
~Animal() {
cout << "Animal Destructor!" << endl;
}
};
struct Cat : public Animal {
void func() override {
cout << "Cat Meow!" << endl;
}
virtual ~Cat() {
cout << "Cat Destructor!" << endl;
}
};
struct Dog : public Animal {
void func() override {
cout << "Dog Bark!" << endl;
}
virtual ~Dog() {
cout << "Dog Destructor!" << endl;
}
};
void foo(Animal* a) {
a->func(); // 调用 Animal 的派生类函数
delete a; // 删除对象,触发析构函数
}
int main() {
// Animal* a = new Cat(); // 创建 Cat 类型对象
// foo(a); // 传入 Animal 指针,调用 foo
Cat c;
c.func();
// a = new Dog(); // 创建 Dog 类型对象
// foo(a); // 传入 Animal 指针,调用 foo
return 0;
}
如果没有定义虚析构函数,使用uniqueptr和sharedptr区别
如果没有定义虚析构函数,使用uniqueptr和sharedptr区别,就算没有定义这个虚析构函数,sharedptr也是会正常析构子类对象的,而uniqueptr则不会。这是因为sharedptr使用了类型擦除技术,在构造其对象的时候已经把子类对象的删除函数塞进去了。而uniqueptr其删除函数(uniqueptr是在模板参数中确定的),如果没有提前写,则会在函数内部调用类似dele,这个delete只会delete基类指针。而sharedptr已经把delete 子类指针塞到构造函数里面了。建议还是虚析构函数写上virtual。
sharedptr例子:
#include <iostream>
#include <memory>
using namespace std;
struct Animal {
virtual void func() {
cout << "Animal!" << endl;
}
~Animal() {
cout << "Animal Destructor!" << endl;
}
};
struct Cat : public Animal {
void func() override {
cout << "Cat Meow!" << endl;
}
virtual ~Cat() {
cout << "Cat Destructor!" << endl;
}
};
struct Dog : public Animal {
void func() override {
cout << "Dog Bark!" << endl;
}
virtual ~Dog() {
cout << "Dog Destructor!" << endl;
}
};
void foo(std::shared_ptr<Animal> a) {
a->func(); // 调用 Animal 的派生类函数
}
int main() {
//sharedptr默认构造时已经确定了要析构谁,两者写法等价。第二种可以理解为默认生成的。
// 等价于auto a = std::shared_ptr<Cat>(new Cat, [](Cat* p){delete p;});
auto a = std::make_shared<Cat>();
foo(a);
return 0;
}
#include <iostream>
#include <memory>
using namespace std;
struct Animal {
virtual void func() {
cout << "Animal!" << endl;
}
~Animal() {
cout << "Animal Destructor!" << endl;
}
};
struct Cat : public Animal {
void func() override {
cout << "Cat Meow!" << endl;
}
virtual ~Cat() {
cout << "Cat Destructor!" << endl;
}
};
struct Dog : public Animal {
void func() override {
cout << "Dog Bark!" << endl;
}
virtual ~Dog() {
cout << "Dog Destructor!" << endl;
}
};
void foo(std::unique_ptr<Animal> a) {
a->func(); // 调用 Animal 的派生类函数
// func中的uniqueptr只知道是Animal指针,不知道Cat指针,析构时大致如下
// delete (Animal*)a.get();
}
int main() {
auto a = std::make_unique<Cat>();
foo(std::move(a));
return 0;
}
Wzero-as-null-pointer-constant
使用 0 或 NULL 来表示空指针是 C++ 的旧方式,在 C++11 及以后版本中,nullptr 是推荐的方式,因为它具有更清晰的语义,并且能避免与整数类型混淆。
#include <iostream>
#include <memory>
using namespace std;
int main() {
int* ptr = 0; // 可能会发出警告:将 0 作为空指针常量使用
return 0;
}
Wparentheses
当你启用这个警告时,编译器会警告在表达式中使用括号时,可能存在一些潜在的优先级错误或多余的括号。
#include <cstdio>
int main() {
int a; // 声明整型变量 a
scanf("%d", &a); // 从用户输入获取值给变量 a
if (a = 9) { // 判断 a 是否等于 9
printf("a 是 9\n");
} else {
printf("a 不是 9\n");
}
return 0; // 程序正常结束
}
如果真的想要这样的表达式成立,不告警。需要在a=0这里再加一对括号
#include <cstdio>
int main() {
int a; // 声明整型变量 a
scanf("%d", &a); // 从用户输入获取值给变量 a
if ((a = 9)) { // 判断 a 是否等于 9
printf("a 是 9\n");
} else {
printf("a 不是 9\n");
}
return 0; // 程序正常结束
}
一般情况下,会这么写
#include <cstdio>
int main() {
int c;
while ((c = getchar()) != EOF) { // 使用 getchar 获取字符,直到遇到文件结束符 (EOF)
putchar(c); // 输出读取的字符
}
return 0; // 返回 0 表示程序正常结束
}
Wempty-body
它会在循环或条件语句的主体为空时发出警告。具体来说,如果你写了一个没有任何执行语句的 for、while 或 if 语句的主体,编译器会提示你这段代码可能存在问题或是一个潜在的错误。
#include <cstdio>
int main() {
int c;
if ((c = getchar()) != EOF);
// 这里没有执行任何操作,应该添加代码来处理输入的字符
return 0;
}
解决办法
#include <cstdio>
int main() {
int c;
if ((c = getchar()) != EOF) {
// 这里没有执行任何操作,应该添加代码来处理输入的字符
}
return 0;
}
Wsign-compare和Wsign-conversion
-Wsign-compare:警告你在比较有符号和无符号整数时可能导致的问题,尤其是在负数和无符号数之间的比较。
-Wsign-conversion:警告你在有符号与无符号类型之间进行隐式转换时,可能会出现不期望的结果,尤其是负数转换为无符号时。
#include <cstdio>
int main() {
unsigned int c = 0; // 定义无符号整数 c,并初始化为 0
int a = -1; // 定义有符号整数 a,并初始化为 -1
//a已然变成一个超大的数,a被迫转成很大的无符号数
if (a < c) { // 比较有符号整数 a 和无符号整数 c
printf("小于!\n");
} else {
printf("大于!\n");
}
return 0;
}
解决办法强转
#include <cstdio>
int main() {
unsigned int c = (unsigned int)-1; // 将 -1 转换为无符号整数
printf("a %u\n", c); // 输出无符号整数 c
return 0;
}
Wformat
用于检测 printf、scanf 等格式化函数的格式字符串是否正确,并且确保传递给这些函数的参数类型与格式字符串的要求匹配。如果格式化字符串和相应参数的类型不匹配,编译器会发出警告。
#include <cstdio>
int main() {
float x = 3.14;
printf("%d\n", x); // 错误:%d 用于输出 int,而 x 是 float 类型
return 0;
}
Wold-style-cast
#include <iostream>
int main() {
double a = 3.14;
int b = (int)a; // 旧式类型转换
std::cout << b << std::endl;
return 0;
}
解决办法:
#include <iostream>
int main() {
double a = 3.14;
int b = static_cast<int>(a); // 使用 static_cast 替代旧式转换
std::cout << b << std::endl;
return 0;
}
Wloop-range-construct(gcc不支持,clang支持)
基于范围的for循环就地想修改,却没有告警
#include <cstdio>
int main() {
int range[] = {3, 4, -5}; // 定义一个整数数组
for (auto x : range) { // 遍历数组并使用范围基 for 循环
x++; // 对每个元素进行加1操作
}
for (auto x : range) { // 再次遍历数组并输出每个元素
printf("%d\n", x); // 使用 %d 输出整数
}
return 0; // 返回 0 表示程序成功结束
}
正确写法
#include <cstdio>
int main() {
int range[] = {3, 4, -5}; // 定义一个整数数组
for (auto& x : range) { // 遍历数组并使用范围基 for 循环
x++; // 对每个元素进行加1操作
}
for (auto x : range) { // 再次遍历数组并输出每个元素
printf("%d\n", x); // 使用 %d 输出整数
}
return 0; // 返回 0 表示程序成功结束
}
Wstrict-aliasing(gcc不支持,clang支持)
检测强转是否安全
#include <iostream>
int main() {
int a = 42;
float* p = reinterpret_cast<float*>(&a); // 将 int* 转换为 float*,违反了严格别名规则
std::cout << *p << std::endl; // 读取时可能导致未定义行为
return 0;
}
正确写法
如果你希望在不同类型之间共享内存(例如想要使用 int 和 char* 共享内存),可以使用 char* 或 unsigned char* 来指向该内存位置,这样就符合严格别名规则。
#include <iostream>
int main() {
int a = 42;
char* p = reinterpret_cast<char*>(&a); // 使用 char* 避免违反严格别名规则
std::cout << *p << std::endl; // 这样做是合法的,但结果可能不是你想要的
return 0;
}
或者,使用 memcpy 来安全地复制数据:
#include <iostream>
#include <cstring>
int main() {
int a = 42;
float b;
std::memcpy(&b, &a, sizeof(int)); // 使用 memcpy 来安全地进行不同类型间的内存复制
std::cout << b << std::endl;
return 0;
}
Wrestrict
Wrestrict地址不允许重叠,memcpy的定义的入参也有这个东西,意思是一样的
#include <cstdio>
#include <cstring> // 包含 memcpy 函数的头文件
void func(short *c) {
// func 函数接受一个 short 类型的指针
// 这里可以添加你想要在 func 中执行的操作
}
int main() {
int i = 0xAABBCCDD; // 初始化 i 为 0xAABBCCDD(一个 32 位整数)
func(reinterpret_cast<short*>(&i)); // 将 i 转换为 short* 类型并传递给 func
memcpy(&i, &i, 4); // 使用 memcpy 将 i 自己的内容复制到 i 中(这个操作没有实际效果)
printf("%#x\n", i); // 输出 i 的十六进制表示(带有前缀 0x)
return 0; // 返回 0 表示程序正常结束
}
Winvalid-memory-model
-Winvalid-memory-model 警告通常出现在以下几种情况:
不符合硬件内存模型的操作: 当你在代码中执行的内存操作不符合目标平台的内存模型时,编译器可能会发出这个警告。例如,使用不恰当的原子操作或者没有正确使用内存屏障(memory barrier)时。
不当的原子操作: 如果你在多线程环境中使用原子操作,但这些操作不符合目标平台对原子性的要求,编译器可能会发出 -Winvalid-memory-model 警告。比如,试图在没有合适的同步机制的情况下对共享内存执行非原子操作。
内存屏障问题: 内存屏障(memory barrier)是用于同步不同线程间对内存的访问。如果在多线程程序中没有正确使用内存屏障,可能会导致线程间数据不一致,编译器可能会警告这种不当操作。
#include <atomic>
int main() {
std::atomic<int> a; // 声明一个原子变量
/*
store 操作用于将值存储到原子变量中,并且它应该与 memory_order_release 或 memory_order_seq_cst 结合使用。
acquire 顺序通常用于加载操作(load),而不是存储操作(store)。
*/
a.store(0, std::memory_order_acquire); // 使用 acquire 内存顺序存储值 0
return 0;
}
- memory_order_release:表示存储操作之后的所有操作不能被重排到此操作之前。
- memory_order_seq_cst:表示最强的同步顺序,它保证了所有操作按顺序执行。
Wsuggest-override
它会建议你在继承类中使用 override 关键字。当你重写基类的虚函数时,使用 override 是一种良好的编码习惯,它可以帮助捕捉潜在的错误。
#include <iostream>
struct Animal {
virtual void speak() { // 需要添加 virtual 关键字,并且函数格式要正确
std::cout << "Animal 说话默认实现\n";
}
virtual ~Animal() = default; // 虚析构函数,确保子类可以正确析构
};
struct Cat : public Animal { // 继承基类 Animal
void speak() { // 使用 override 关键字以确保正确重写
std::cout << "Cat 说:喵!\n";
}
};
int main() {
Cat cat;
cat.speak(); // 输出 "Cat 说:喵!"
return 0;
}
警告告诉你覆盖了别人的override,需要加override关键字
Wconversion
Wconvention内置了Wenum-convention会检测从int到枚举值的转化
#include <iostream>
enum Color { // 修正 `enun` -> `enum`,以及拼写错误 `Colar` -> `Color`
RED,
GREEN,
BLUE
};
void foo(Color color) { // 修正 `vuid` -> `void`,参数拼写 `culor` -> `color`
(void)color; // 避免未使用变量的警告
}
struct Animal {
virtual void speak() {
std::cout << "Animal 说话默认实现\n";
}
virtual ~Animal() = default; // 虚析构函数,确保子类析构正确
};
struct Cat : public Animal {
void speak() override {
std::cout << "Cat 说:喵!\n";
}
};
int main() {
Cat cat;
cat.speak(); // 调用 Cat 的 speak() 方法,输出 "Cat 说:喵!"
foo(1); // 需要传递一个 Color 类型的参数
return 0;
}
Wattributes
Wattribute(Wall自动开启)
eg: 定义一个人static但是却不用
#include <iostream>
enum Color { // 修正 `enun` -> `enum`,以及拼写错误 `Colar` -> `Color`
RED,
GREEN,
BLUE
};
void foo(Color color) { // 修正 `vuid` -> `void`,参数拼写 `culor` -> `color`
(void)color; // 避免未使用变量的警告
}
struct Animal {
virtual void speak() {
std::cout << "Animal 说话默认实现\n";
}
virtual ~Animal() = default; // 虚析构函数,确保子类析构正确
};
struct Cat : public Animal {
void speak() override {
std::cout << "Cat 说:喵!\n";
}
};
static void foo1(Color color) { // 修正 `vaid` -> `void`
int iColor = static_cast<int>(color); // 修正 `icolorstatic_cast<int>(calor);`
printf("%d\n", iColor); // 修正 `print+("%e\n"icolor);`
}
int main() {
Cat cat;
cat.speak(); // 调用 Cat 的 speak() 方法,输出 "Cat 说:喵!"
foo(RED); // 需要传递一个 Color 类型的参数
return 0;
}
解决办法
#include <iostream>
enum Color { // 修正 `enun` -> `enum`,以及拼写错误 `Colar` -> `Color`
RED,
GREEN,
BLUE
};
void foo(Color color) { // 修正 `vuid` -> `void`,参数拼写 `culor` -> `color`
(void)color; // 避免未使用变量的警告
}
struct Animal {
virtual void speak() {
std::cout << "Animal 说话默认实现\n";
}
virtual ~Animal() = default; // 虚析构函数,确保子类析构正确
};
struct Cat : public Animal {
void speak() override {
std::cout << "Cat 说:喵!\n";
}
};
[[maybe_unused]] static void foo1(Color color) { // 修正 `vaid` -> `void`
int iColor = static_cast<int>(color); // 修正 `icolorstatic_cast<int>(calor);`
printf("%d\n", iColor); // 修正 `print+("%e\n"icolor);`
}
int main() {
Cat cat;
cat.speak(); // 调用 Cat 的 speak() 方法,输出 "Cat 说:喵!"
foo(RED); // 需要传递一个 Color 类型的参数
return 0;
}
头文件就地定义一个函数,附在头里
inline void foo1(Color color) { // 修正 `vaid` -> `void`
int iColor = static_cast<int>(color); // 修正 `icolorstatic_cast<int>(calor);`
printf("%d\n", iColor); // 修正 `print+("%e\n"icolor);`
}
Wmissing-declarations
Wmissing-declarations,强制声明在头文件,定义在cc文件
#include <iostream>
enum Color{};
void foo(Color color) { // 这里的 `foo` 没有提前声明
int iColor = static_cast<int>(color);
printf("%d\n", iColor);
}
int main()
{
return 0;
}
解决办法
#include <iostream>
enum Color{};
void foo(Color color);
void foo(Color color) { // 这里的 `foo` 没有提前声明
int iColor = static_cast<int>(color);
printf("%d\n", iColor);
}
int main()
{
return 0;
}
就地CC源文件定义函数
使用static
#include <iostream>
enum Color { // 修正 `Calor` -> `Color`
RED,
GREEN,
BLUE
};
static void foo(); // 声明 `foo`,否则 `-Wmissing-declaration` 可能会报错
void func(Color color) { // 修正 `Calor` -> `Color`
foo(); // 调用 `foo()`
int iColor = static_cast<int>(color); // 修正 `statis_cast` -> `static_cast`
printf("%d\n", iColor); // 修正 `"%t\a"` -> `"%d\n"`
}
static void foo() { // `foo` 的实现
std::cout << "foo() 被调用了!" << std::endl;
}
int main() {
func(RED); // 调用 `func`
return 0;
}
或者放在匿名空间里面
#include <iostream>
enum Color {
RED,
GREEN,
BLUE
};
// ✅ 使用匿名命名空间限制 `foo` 作用域
namespace {
void foo() {
std::cout << "foo() 被调用了!" << std::endl;
}
}
void func(Color color) {
foo(); // 调用 `foo()`
int iColor = static_cast<int>(color);
printf("%d\n", iColor);
}
int main() {
func(RED);
return 0;
}
Warray-bounds
编译期查看array越界访问
#include <iostream>
#include <array>
int main() {
std::array<int, 4> a{1,2};
std::cout << std::get<4>(a) << std::endl;
return 0;
}
运行期查看数组越界
#include <iostream>
#include <array>
int main() {
std::array<int, 4> a{1,2};
std::cout << a[4]<< std::endl;
return 0;
}
gcc都查不出来
Wdangling-pointer=2(gcc无法检测出来)
这个警告选项开启后,编译器会检查代码中可能的悬空指针情况,并在检测到潜在问题时给出警告。-Wdangling-pointer=2 表示开启警告的 中等级别,编译器将会尝试找出更为明显的悬空指针问题
#include <iostream>
static int* func() {
int* p = new int;
delete p;
return p;
}
int main() {
int* p = func();
std::cout << *p << '\n';
return 0;
}
gcc无法检测出来
Wmismatched-new-delete
new数组,但是没有delete数组,
#include <iostream>
int main() {
int* ptr = new int[10]; // 使用 new[] 分配内存
delete ptr; // 错误:应该使用 delete[] 释放内存,而不是 delete
return 0;
}
Wvla :variable-length-array
可变长度数组是指数组的大小在运行时决定,而不是在编译时确定。
#include <iostream>
int main() {
}
void example(int n) {
int arr[n]; // 使用 n 作为数组大小,n 在运行时决定
arr[0] = 10;
}
Wreorder,关闭的话Wno-reorder
用于检测类构造函数中成员变量的初始化顺序是否与它们在类中声明的顺序一致。
#include <iostream>
class MyClass {
int a;
int b;
public:
MyClass(int x, int y) : b(y), a(x) { // 初始化顺序错误
std::cout << "a: " << a << ", b: " << b << std::endl;
}
};
int main() {
MyClass obj(10, 20);
return 0;
}
析构函数默认不抛出异常的,除非显示加上noexcept
class MyClass {
public:
~MyClass() noexcept(false) { // 显式声明析构函数为 noexcept
// 安全地执行清理操作,确保不会抛出异常
}
};
Werror=return-type
#include <iostream>
int main() {
}
int foo() {
// 缺少返回语句
// UB:返回值不void的函数不写return
}
Wswitch(gcc无法检测出来)
Wswitch(默认在Wall中开启),有时候修改了枚举量,但是某个函数switch忘记写了,可以增加告警。漏了case就会报错
#include <iostream>
int main() {
}
int foo(int x) {
switch (x) {
case 1:
return 10;
case 2:
return 20;
// 缺少 default 分支
}
return 0;
}
-Wimplicit-fallthrough(gcc检测不出来)
用于检测 switch 语句中的 隐式穿透(implicit fallthrough)。默认情况下,在 switch 语句中,如果没有显式地使用 break 或其他控制流语句跳出某个 case,程序会继续执行下一个 case 标签,直到遇到 break 或 return。
#include <iostream>
int main() {
}
int foo(int x) {
switch (x) {
case 1:
return 10;
case 2:
return 20;
case 3:
return 30; // 如果没有 break 或注释,可能会引起隐式穿透
}
return 0;
}
Wshadow
需要强制开,覆盖掉了全局变量
#include <iostream>
int x = 10; // 外部变量
void foo() {
int x = 20; // 局部变量遮蔽了外部变量
printf("%d\n", x); // 输出 20,而不是 10
}
int main() {
}
Wshadow=local
全局变量覆盖不警告,局部变量覆盖警告,一般不开启这个
#include <iostream>
int main() {
int i=0;
{
std::string i;
}
}
#include <iostream>
int main() {
int i=0;
{
int i =1;
}
}
下面的例子开启这个Wshadow=local不太好用
#include <iostream>
int main() {
int i=0;
{
auto f= =[](int i){};
}
}