Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
665 views
in Technique[技术] by (71.8m points)

c - How to stop GCC complaining about "directive output may be truncated" in snprintf() call?

I'm using GCC 9.2.0 on both an ancient Linux (RedHat 5.2) and modern macOS 10.14.6 Mojave, and I get the same complaint on both.

#include <stdio.h>
#include <time.h>

struct Example
{
    /* ... */
    char mm_yyyy[8];    /* Can't be changed */
    /* ... */
};

extern void function(struct tm *tm, struct Example *ex);

void function(struct tm *tm, struct Example *ex)
{
    snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
             tm->tm_mon + 1, tm->tm_year + 1900);
}

When compiled with -Wall and any optimization (so not with -O0 and not with no optimization option at all), the compiler says:

$ gcc -O -Wall -c so-code.c 
so-code.c: In function ‘function’:
so-code.c:15:49: warning: ‘%d’ directive output may be truncated writing between 1 and 11 bytes into a region of size 8 [-Wformat-truncation=]
   15 |     snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
      |                                                 ^~
so-code.c:15:48: note: directive argument in the range [-2147483647, 2147483647]
   15 |     snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
      |                                                ^~~~~~~
so-code.c:15:48: note: directive argument in the range [-2147481748, 2147483647]
so-code.c:15:5: note: ‘snprintf’ output between 4 and 24 bytes into a destination of size 8
   15 |     snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   16 |              tm->tm_mon + 1, tm->tm_year + 1900);
      |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$

At one level, fair enough; if tm->tm_mon contained a value outside the range 0 to 99 (or -1 to -9), then more than two bytes would be written to the output buffer, or if tm->tm_year + 1900 required more than 4 digits, then there would be truncation/overflow. However, the time values are known to be valid (month 0 to 11; year + 1900 in the range 1970 to 2100, for sake of concreteness; the range of years is actually smaller — 2019 .. 2025 or thereabouts), so the concern is not actually warranted.

Is there a way to suppress the warning without resorting to code such as:

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignore "-Wformat-overflow" /* Or "-Wformat-truncation" */
#endif

    snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
             tm->tm_mon + 1, tm->tm_year + 1900);

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

The #ifdef lines are necessary because the code has to be compiled by other compilers (notably XLC 13.x on AIX) which complain about unknown pragmas, even though they're not really supposed to. (More formally, they're not required to complain and should accept the code ignoring the unknown pragma, but they pass comments about not recognizing the pragma, which defeats the goal of clean compilation.)

Just for kicks; if you change the function from returning void to returning int, and if you then return snprintf(…);, the error is not generated. (This surprises me — I'm not sure I understand why this is not a problem too. I guess it has to do with 'the return value from snprintf() is returned so it could be checked and the overflow spotted, therefore, but it is a bit surprising.)

Unfortunately, this is an MCVE (Minimal, Complete, Verifiable Example; the code it is extracted from is much bigger, and changing the data structure is not an option — and this is just one step among many in the function where it appears.

I suppose I could write a microscopic function to call snprintf() and return the value (which would be ignored), but:

  • Is there any other viable alternative that works?
  • Is there a way to tell GCC that the range of the variables passed to snprintf() et al is safer than the worst case scenario?
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

With compilers that check the range of possible values, use % to quickly limit the range.

% some_unsigned_N insures the output is in [0... N-1].

Note that % some_pos_int_N output is in the (-N ...N) range so recommend unsigned math to avoid the '-' sign.

snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d", 
    //  tm->tm_mon + 1, tm->tm_year + 1900);
    (tm->tm_mon + 1)%100u, (tm->tm_year + 1900)%10000u);

May also want to use "%u" should some_unsigned_N near INT_MAX.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...