-Werror in C

Table of Content:
  1. 指针
    1. 整数到指针转换
    2. void *指针上的算术运算
    3. void **指针
  2. sign-compare有符号整数和无符号整数
  3. const的问题
    1. redefined
  4. IO
    1. printf
    2. sprintf
    3. missing terminating “ char
  5. 字面数值
    1. 后缀

典型的C语言的Warning的解决方法

在C语言中,经常会碰到各种各样的错误,但是一般遇到warning,估计就会被直接忽视掉.
但是这些都隐藏着潜在的错误,说不定在什么情况下就会爆发出来.

本文总结一些常见的warning,并且分析下warn在何处,以及如何进行修改来避免这些warning.

指针

整数到指针转换

在一般机器上int类型为32bit.指针类型的长度则取决于机器为32bit或者64bit.

显然在32bit上,二者可以安全进行转换,但是在64bit上,从64bit指针转换为32bit整数,则会被截断.从32bit整数到64bit指针转换,则会补0或补1.

修改方法可以采用”intptr_t”类型.

在C里面,可能没有定义这个类型,可以根据ctype中的宏来自己定义一个宏或者类型.

void *指针上的算术运算

一般指针的算术运算,会自动按照其类型的长度进行.

assert(sizeof(int) == 4);
int *p = NULL;
p++;
assert(p == 4);

而void *类型的指针,因为没有类型,则无法进行这个运算.在warning状态下,其实是按照字节进行的,也就是当做char *指针进行算数运算.

因此对于buffer的指针(最常见的不知道类型,但是有需要偏移计算的场景).
建议使用char *类型或者unsingned char*类型.

以上的两种问题,主要在于分类/定位不明.

  • buffer/data的内存区域,使用unsigned char*,
  • 未知/不定的指针类型,void *
  • 使用整数表示指针类型,彻底的错误用法,避免.

但是,如果作为handle之类使用,可以考虑使用整数来表示.但是也应该避免,内部维护一个表,以index作为handle更好.这种方法,避免外部任意操作内部资源的可能.

void **指针

void *的指针是通用的(generic),也就是意味着可以从/向指针类型转换,而没有warning.

有一些使用方法,就是利用指针(引用)来从函数中返回数据.结合void *类型,就需要使用void **类型的指针.

如果我们希望返回到一个int *的指针上来.这个时候就会碰到错误

void mymalloc(void **p) {
    *p = malloc(1024);
}

int *p;
mymalloc(&p);
// 不兼容的指针,从int **类型到void **类型.

这个错误在于错误的将void **类型理解为一个通用的指针的指针.实际上void **类型,是指向void *类型的,不是通用的.

正确的方法有两种,第一种就是在这种情况下避免使用这种将结果传出的方法.放到上面的例子,就是原始的malloc函数,直接利用返回值进行返回.

第二种就是利用一个void *指针来间接达到目的.

int *p;
void *pv;
func(&pv);
p = pv; // key

这种方式的关键在于在key标示的那一行,其实进行了类型转换.
但是这一步类型转换时从 void *类型,转换到int *类型.
这种转换则是可以的.

sign-compare有符号整数和无符号整数

这个错误是显然的.修改方法也是显然的.

关键是在一般表达为正整数的场合,都坚持使用size_t类型.这个类型其实等价于unsigned long类型.

常见的例子在于数组的下标,一般应采用unsigned的类型.

另外对于这个类型的打印输出,使用”%lu”类型,而不是”%d”哦.

const的问题

redefined

这个就是宏被重复定义了.

如果出现这个问题,请确保你的头文件的层次结构设计是合理的.

解决办法当然不能是简单的删除重复定义的宏.而是良好的设计和习惯.

  • 头文件使用#ifdef _HEADER #endif之类的包含.
  • 使用头文件的时候,遵循一定的顺序.建议顺序是从大到小,从最基础到最顶层的顺序.
  • 使用#ifndef #endif来避免这种情况.

如果你的模块化和层次设计没有问题.并且做到了第一条,就基本不会有问题的.
后面两条都是补救的办法.

IO

printf

打印的时候注意类型和后面的参数格式要对应.

sprintf

缓冲区溢出.
使用snrpintf避免这类问题.

这种问题很多,类似的只是简单的列一下.

  • read/fgets的返回值(检查是否成功,以及量)
  • gets -> fgets

missing terminating “ char

C语言提供过一个特性,就是自动拼接相邻的字符串.

"abc" "def"
"abcdef"

上面的两个写法是等效.因此在长字符串换行的时候,可以使用这个得到更为美观的代码.同时避免原本想表示缩进的空格/tab嵌入在字面字符串中.

printf("line0 string\n"
       "line1 string\n", ...);

字面数值

后缀

默认情况下,C语言中直接输入的数字,也是有类型的.

- 默认          什么修饰都没有(默认的)为10进制有符号整数
- 0前缀         8进制整数(原始的00前缀,是错的)
- 0x前缀        表示16进制整数
- u后缀         表示无符号整数
- l后缀         表示long型

这点看上去没有什么,但是可能会碰到坑哦.
我就看到一个还不错的工程师,犯了00前缀的问题.

0,0x前缀在printf时候,使用#符号修饰的时候就会增加这个0,0x的前缀修饰符。

这不是bug,这是一个特性。 :)

其实还有很多warning,这里就挑出几个可以说说的讲讲吧.