gdate error: format not a string literal -Werror=format-nonliteral 错误详解

本文探讨了在使用glib进行交叉编译时遇到的format-nonliteral错误,发现与编译器的-Werror=format选项有关,高版本GCC对格式化字符串检查严格。作者通过实例和源码分析揭示了问题本质,并提供了编译器版本对比和解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  项目中用到了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]
  

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙赤子

你的小小鼓励助我翻山越岭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值