项目中用到了glib,在对其交叉编译时,没想到出现了如下错误:
gdate.c: In function 'g_date_strftime':
gdate.c:2497:7: error: format not a string literal, format string not checked [-Werror=format-nonliteral]
tmplen = strftime (tmpbuf, tmpbufsize, locale_format, &tm);
^~~~~~
cc1: some warnings being treated as errors
网上查找了不少资料,结果所有的方案像是都来自同一个人,大意都是说这是一个编译器版本导致的问题,高版本gcc 6以上可能就会报该错误,并提供了打补丁的解决方案,具体就是在调用前后加入
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic pop
但是没有说明该选项是何意思,也没有说明报错到底是啥原因。
的确,加上上述宏后,可以编译通过了。但是本着知其然还要知其所以然的精神,我们追一下这个问题的本因,在这里做个补充说明。
多说一句,通过该问题分析,博主觉得,如果能够多花点时间,搞清楚问题的真正原因,那么自我提升速度会是远远大于“拿来主义”的,毕竟追本溯源的过程最能实现”梯度“升级。
闲话就说到这里,下面进入正题。
从上面的现象和网络上的分析及解决方案,我们可以得出以下一些线索:
1 这个是一个编译器选项引入的问题
2 错误描述的大概意思是需要一个格式化的字符串参数,但是实际给的似乎不符合要求
3 从-Werror 结合some warnings being treated as errors这句来看,似乎是将警告按错误来对待
4 这个问题跟编译器版本相关
那我们就从上面几个线索出发,看看问题的原因到底是什么。
网络搜索了GCC的警告选项后,看到了该问题跟printf scanf strftime(正是这里调用的函数)strfmon等使用格式字符串做参数的函数相关的选项
参考连接:
https://blog.csdn.net/qq_17308321/article/details/79979514
https://www.cnblogs.com/Dennis-mi/articles/7150321.html
这里正好用到了strftime函数,那我们看看这个函数有啥特殊地方;另外,可不可以把这函数单独拎出来,聚焦问题点,方便追踪分析。
查看Linux下关于这个函数的说明,参见https://man7.org/linux/man-pages/man3/strftime.3.html
除了有一句 Nowadays, gcc(1) provides the -Wno-format-y2k option to prevent the warning, so that the above workaround is no longer required.没有更多与上述警告选项相关的描述。
但是,提供了一个函数使用例子,我们正好可以拿这个例子来验证问题,同时验证是否编译器版本大于6才会有上述编译错误。
例子程序如下:(注释行是调试单独加的)
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char outstr[200];
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
if (tmp == NULL) {
perror("localtime");
exit(EXIT_FAILURE);
}
if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
//if (strftime(outstr, sizeof(outstr), "%C", tmp) == 0) {
fprintf(stderr, "strftime returned 0");
exit(EXIT_FAILURE);
}
printf("Result string is \"%s\"\n", outstr);
exit(EXIT_SUCCESS);
}
先用当前编译器编译上述代码,没有问题,运行,没有问题
用date命令查看当前日期
Thu Jan 28 03:14:02 PST 2021
./a.out '%j'
Result string is "028"
./a.out '%G'
Result string is "2021"
./a.out '%C'
Result string is "20"
可见程序运行没有问题。
那为啥编译glib时就会有问题呢,显然,这里涉及了编译选项。我们看看glib中生成的Makefile,看看GCC用了那些选项
搜索Werror,看到了这么一行
GLIB_WARN_CFLAGS = -Wall -Wstrict-prototypes -Werror=declaration-after-statement -Werror=missing-prototypes -Werror=implicit-function-declaration -Werror=pointer-arith -Werror=init-self -Werror=format=2 -Werror=missing-include-dirs
显然,这里的-Werror=format=2就是引发问题的点。带该选项,重新编译上述程序:
arm-none-linux-gnueabi-gcc test.c -Werror=format=2
test.c: In function ‘main’:
test.c:20:5: error: format not a string literal, format string not checked [-Werror=format-nonliteral]
if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
^~
cc1: some warnings being treated as errors
问题复现了。
再看看编译器版本
arm-none-linux-gnueabi-gcc --version
arm-none-linux-gnueabi-gcc 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
这里是6.3.0
我们再用低版本的编译器试试,看看问题咋样
arm-linux-gcc test.c -Werror=format=2
没有报任何错误和输出,编译成功。
同样,再看看编译器版本
arm-linux-gcc --version
arm-linux-gcc (ctng-1.8.1-FA) 4.5.1
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
这里是 4.5.1
确实,该问题与编译器版本有关。这一点得到了验证。
进一步的,我们将上述 这行
if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
引入问题的代码换成
if (strftime(outstr, sizeof(outstr), "%C", tmp) == 0) {
然后再带着选项编译,这次,两个编译器都顺利完成编译。
到这里,我们得到了”肤浅结论“:
问题就是最开始推测的,加入-Werror=format=2后,会对格式字符串的输入做严格检查。如果编译器看着它不像是格式字符串,满足警告的情况下,会将该警告作为错误处理,从而停止继续编译。
而网络上给的解决方案,就是让编译器在push和pop宏之间,忽略上述警告,从而规避问题。
当然,你也可以修改代码,这可能就比较麻烦,需要看明白哪行代码的前后逻辑;还可以修改生成的Makefile,去掉上述编译选项。总之,知道问题原因后,就可以根据自身情况,做出处理。
到目前,我们只是拿到了肤浅的结论。深入的分析,就需要具体看看这个选项的解释说明了。有关这方面,自然是官方文档最权威了,不过就是都是英文的。
就上述用的两个编译器版本,该选项说明连接如下:
https://gcc.gnu.org/onlinedocs/gcc-4.5.1/gcc/Warning-Options.html
https://gcc.gnu.org/onlinedocs/gcc-6.5.0/gcc/Warning-Options.html
想深入了解原因的读者,可以从这两篇官方文档作为切入点,获取更准确详细的解释。
多提一句,从官方文档看,这里的 -Wformat=2 指代了两类错误:
Enable -Wformat plus additional format checks. Currently equivalent to -Wformat -Wformat-nonliteral -Wformat-security -Wformat-y2k.
其中一个就是报错时显示的[-Werror=format-nonliteral]