Path: csiph.com!weretis.net!feeder8.news.weretis.net!eternal-september.org!reader02.eternal-september.org!.POSTED!not-for-mail From: Tim Rentsch Newsgroups: comp.lang.c Subject: Re: Why does C allow structs to have a tag? Date: Fri, 01 Oct 2021 15:04:41 -0700 Organization: A noiseless patient Spider Lines: 116 Message-ID: <8635pktn9i.fsf@linuxsc.com> References: <87czqnlp2m.fsf@bsb.me.uk> <875ywci8k0.fsf@bsb.me.uk> <63773d19-8176-43a0-9c6d-47f72b1e1c10n@googlegroups.com> <87tujwgldp.fsf@bsb.me.uk> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Injection-Info: reader02.eternal-september.org; posting-host="394007ef4ff0447a8819fcdafe9c3245"; logging-data="22331"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1/0iAhKj7qOhu9f5V9p3PQXIe9D1n06lfI=" User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.4 (gnu/linux) Cancel-Lock: sha1:Wojrm41GlUlkqLlWHRYYl02g/uk= sha1:haGCTXl1oWhiljHiIAtRJqhY9+g= Xref: csiph.com comp.lang.c:162923 Michael S writes: [...] > The purpose of my_pritntf() is to prevent buffer overflows [...] > rather than to always output something meaningful. [...] Sounds useful and practical. > [..some whitespace added in what follows..] > > #include > #include > #include > > #include "my_snprintf.h" > > // my_snprintf - Write formatted output to sized buffer > // my_snprintf is similar to snprintf() with following modifications > // > // 1. Argument n is signed. For n <= 0 the function does nothing > // and returns 0 > // > // 2. Return value is always a number of characters that have been > // into s, not counting the terminating null character. > // > // The rest of description applies to case of positive n. > // Unlike snprintf(), if an encoding error occurs, a 0 is returned > // and the terminating null character is written to s[0]. > // Unlike snprintf(), if n had not been sufficiently large, a > // value of n-1 is returned and the terminating null character is > // written to s[n-1]. > > int > my_snprintf(char * s, ptrdiff_t n, const char * format, ...) > { > if (n <= 0) > return 0; > if (n > INT_MAX) > n = INT_MAX; > > va_list args; > va_start(args, format); > int ret = vsnprintf(s, n, format, args); > if (ret < 0) > { // encoding error. > // since our code uses no UNICODE, it should never happen, > // but let's handle it nevertheless > s[0] = 0; // may be, unnecessary > return 0; > } > > if (ret >= n) { > s[n-1] = 0; // most likely unnecessary > return n-1; > } > > return ret; > } Some short comments: One: signed types have issues. Rather than using a signed type for the parameter 'n', use a large unsigned type such as unsigned long long. Doing that still allows arguments having a signed type to be checked for negative values, following the rules for conversion of a signed value to an unsigned type. Two: va_start() always requires a corresponding va_end(). Three: when supplying a *printf() function, it is customary to supply also a corresponding *vprintf() function, which makes writing the non-v form straightforward. It's two functions for the price of one (plus only a little bit). Also it makes the habit of using va_end() along with va_start() fall out with essentially no effort. To illustrate these points in C code, here is a revised writing of my_snprintf(), along with my_vsnprintf(); // --- cut here --- #include #include #include typedef unsigned long long ULL; extern int my_vsnprintf( char *, ULL, const char *, va_list ); int my_snprintf( char * s, ULL n, const char * format, ... ){ va_list args; int r; va_start( args, format ); r = my_vsnprintf( s, n, format, args ); va_end( args ); return r; } int my_vsnprintf( char * s, ULL n0, const char * format, va_list args ){ if( n0 > -1ULL>>1 || n0 == 0 ) return 0; size_t n = n0 > INT_MAX ? INT_MAX : n0; int ret = vsnprintf( s, n, format, args ); return ret < 0 ? s[0] = 0, 0 : ret >= n ? s[n-1] = 0, n-1 : ret; }