Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c > #107805 > unrolled thread

How to fix: [-Wformat-nonliteral]

Started byMehdiAmini <atorrses@gmail.com>
First post2017-04-07 12:20 +0430
Last post2017-04-08 20:34 +0430
Articles 20 on this page of 55 — 9 participants

Back to article view | Back to comp.lang.c


Contents

  How to fix: [-Wformat-nonliteral] MehdiAmini <atorrses@gmail.com> - 2017-04-07 12:20 +0430
    Re: How to fix: [-Wformat-nonliteral] Barry Schwarz <schwarzb@dqel.com> - 2017-04-07 01:28 -0700
    Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-07 11:58 +0200
      Re: How to fix: [-Wformat-nonliteral] Ben Bacarisse <ben.usenet@bsb.me.uk> - 2017-04-07 11:43 +0100
        Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-07 13:35 +0200
      Re: How to fix: [-Wformat-nonliteral] Ben Bacarisse <ben.usenet@bsb.me.uk> - 2017-04-07 13:50 +0100
        Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-07 15:54 +0200
          Re: How to fix: [-Wformat-nonliteral] Ben Bacarisse <ben.usenet@bsb.me.uk> - 2017-04-07 19:35 +0100
            Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-08 13:41 +0200
              Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-10 13:52 -0700
                Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-11 00:18 +0200
                  Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-10 15:43 -0700
                    Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-10 16:10 -0700
                      Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-10 16:26 -0700
                    Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-11 11:33 +0200
                      Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-11 08:37 -0700
                        Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-13 00:20 +0200
                          Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-12 15:55 -0700
                            Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-12 18:17 -0700
                            Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-14 12:27 +0200
                              Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-14 11:57 -0700
                                Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-17 18:08 +0200
                                  Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-17 09:38 -0700
                          Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-12 18:13 -0700
                            Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-13 07:37 -0700
                              Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-13 08:43 -0700
                                Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-13 11:24 -0700
                                  Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-13 11:49 -0700
                                    Re: How to fix: [-Wformat-nonliteral] Robert Wessel <robertwessel2@yahoo.com> - 2017-04-13 14:43 -0500
                                      Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-13 13:26 -0700
                                      Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-13 13:32 -0700
                                        Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-13 14:27 -0700
                                          Re: How to fix: [-Wformat-nonliteral] Robert Wessel <robertwessel2@yahoo.com> - 2017-04-13 17:39 -0500
                                            Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-13 16:16 -0700
                                      Re: How to fix: [-Wformat-nonliteral] Tim Rentsch <txr@alumni.caltech.edu> - 2017-04-16 17:53 -0700
                                    Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-13 13:19 -0700
                                    Re: How to fix: [-Wformat-nonliteral] Tim Rentsch <txr@alumni.caltech.edu> - 2017-04-16 17:49 -0700
                                Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-17 18:14 +0200
                                  Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-17 11:54 -0700
                                    Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-17 22:41 +0200
                                      Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-17 15:02 -0700
                            Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-14 12:40 +0200
                              Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-14 12:10 -0700
                                Re: How to fix: [-Wformat-nonliteral] Keith Thompson <kst-u@mib.org> - 2017-04-14 13:22 -0700
                                  Re: How to fix: [-Wformat-nonliteral] supercat@casperkitty.com - 2017-04-14 17:14 -0700
                        Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-13 00:20 +0200
        Re: How to fix: [-Wformat-nonliteral] MehdiAmini <atorrses@gmail.com> - 2017-04-07 20:29 +0430
          Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-07 18:42 +0200
            Re: How to fix: [-Wformat-nonliteral] MehdiAmini <atorrses@gmail.com> - 2017-04-08 08:04 +0430
              Re: How to fix: [-Wformat-nonliteral] Richard Heathfield <rjh@cpax.org.uk> - 2017-04-08 07:51 +0100
                Re: How to fix: [-Wformat-nonliteral] MehdiAmini <atorrses@gmail.com> - 2017-04-08 12:11 +0430
                  Re: How to fix: [-Wformat-nonliteral] Richard Heathfield <rjh@cpax.org.uk> - 2017-04-08 11:38 +0100
                    Re: How to fix: [-Wformat-nonliteral] Ben Bacarisse <ben.usenet@bsb.me.uk> - 2017-04-08 18:52 +0100
              Re: How to fix: [-Wformat-nonliteral] David Brown <david.brown@hesbynett.no> - 2017-04-08 13:48 +0200
                Re: How to fix: [-Wformat-nonliteral] MehdiAmini <atorrses@gmail.com> - 2017-04-08 20:34 +0430

Page 1 of 3  [1] 2 3  Next page →


#107805 — How to fix: [-Wformat-nonliteral]

FromMehdiAmini <atorrses@gmail.com>
Date2017-04-07 12:20 +0430
SubjectHow to fix: [-Wformat-nonliteral]
Message-ID<oc7g6c$lpq$1@dont-email.me>
Hi,

Compiling this function using Clang:

noreturn void my_err(const char * const s ,...)
{

     va_list args;
     va_start(args, s);

     if( (s==NULL) || ( *s=='\0') )
     {
         fprintf(stderr,"Please provide a proper message for my_err 
function.\n");
         exit(EXIT_FAILURE);
     }


     vfprintf(stderr,s,args);

     fprintf(stderr,"\n");

     va_end(args);

     exit(EXIT_FAILURE);
}

I get:
warning: format string is not a string literal [-Wformat-nonliteral]
     vfprintf(stderr,s,args);
                     ^

How to fix the warning?

-- 
www.my-c-codes.com/

Farewell.

[toc] | [next] | [standalone]


#107806

FromBarry Schwarz <schwarzb@dqel.com>
Date2017-04-07 01:28 -0700
Message-ID<3djeec5to8uf3vuui5vb8evcqtjb51ilk1@4ax.com>
In reply to#107805
On Fri, 7 Apr 2017 12:20:25 +0430, MehdiAmini <atorrses@gmail.com>
wrote:

>Hi,
>
>Compiling this function using Clang:
>
>noreturn void my_err(const char * const s ,...)
>{
>
>     va_list args;
>     va_start(args, s);
>
>     if( (s==NULL) || ( *s=='\0') )
>     {
>         fprintf(stderr,"Please provide a proper message for my_err 
>function.\n");
>         exit(EXIT_FAILURE);
>     }
>
>
>     vfprintf(stderr,s,args);
>
>     fprintf(stderr,"\n");
>
>     va_end(args);
>
>     exit(EXIT_FAILURE);
>}
>
>I get:
>warning: format string is not a string literal [-Wformat-nonliteral]
>     vfprintf(stderr,s,args);
>                     ^
>
>How to fix the warning?

Specify -Wno-format-nonliteral

-- 
Remove del for email

[toc] | [prev] | [next] | [standalone]


#107807

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-07 11:58 +0200
Message-ID<oc7nm7$d6i$1@dont-email.me>
In reply to#107805
On 07/04/17 09:50, MehdiAmini wrote:
> Hi,
> 
> Compiling this function using Clang:
> 
> noreturn void my_err(const char * const s ,...)
> {
> 
>     va_list args;
>     va_start(args, s);
> 
>     if( (s==NULL) || ( *s=='\0') )
>     {
>         fprintf(stderr,"Please provide a proper message for my_err
> function.\n");
>         exit(EXIT_FAILURE);
>     }
> 
> 
>     vfprintf(stderr,s,args);
> 
>     fprintf(stderr,"\n");
> 
>     va_end(args);
> 
>     exit(EXIT_FAILURE);
> }
> 
> I get:
> warning: format string is not a string literal [-Wformat-nonliteral]
>     vfprintf(stderr,s,args);
>                     ^
> 
> How to fix the warning?
> 

Another poster showed you how to disable the warning.  But you might
also like to consider /why/ clang has this warning - and if you would
rather change the logic of your function instead of disabling the warning.

The format string of the printf functions is like a little specialised
interpreted language.  Get the syntax wrong, or get a mismatch in the
arguments, and you can break all sorts of things in all sorts of ways.
This can be due to simple bugs, or intentional security breaches.  And
in a function like this one, which will typically rarely be called
during testing, it is easy to get things wrong.

So it would be /much/ safer to have the function as:

noreturn void my_err_s(const char * const s)
{
	if ((s == NULL) || (*s == '\0')) {
		fprintf(stderr, "Please provide a proper message for "
			 "my_err function.\n");
		exit(EXIT_FAILURE);
	}
	fprintf(stderr, "%s\n", s);
	exit(EXIT_FAILURE);
}

#define my_errPrintfBuffLen 256
#define my_err(f, x...) \
	do { \
		char buff[my_errPrintfBuffLen]; \
		snprintf(buff, sizeof(buff) - 1, f, ## x); \
		buff[my_errPrintfBuffLen - 1] = 0; \
		my_err_s(buff); \
	} while (0)

This makes any formatting be handled locally in the function that calls
my_err, where the compiler can see the format string as a literal and
check the format against the actual parameters used.  (There is, IIRC, a
slight gcc'ism in the token pasting here to get the commas right even if
my_err is called with no extra parameters.  But I expect clang supports
the same feature.)

[toc] | [prev] | [next] | [standalone]


#107808

FromBen Bacarisse <ben.usenet@bsb.me.uk>
Date2017-04-07 11:43 +0100
Message-ID<87shlkv6om.fsf@bsb.me.uk>
In reply to#107807
David Brown <david.brown@hesbynett.no> writes:

> On 07/04/17 09:50, MehdiAmini wrote:
<snip>
>>     vfprintf(stderr,s,args);
<snip>
>> I get:
>> warning: format string is not a string literal [-Wformat-nonliteral]
>>     vfprintf(stderr,s,args);
<snip>

> Another poster showed you how to disable the warning.
<snip>
> So it would be /much/ safer to have the function as:
>
> noreturn void my_err_s(const char * const s)
> {
> 	if ((s == NULL) || (*s == '\0')) {
> 		fprintf(stderr, "Please provide a proper message for "
> 			 "my_err function.\n");
> 		exit(EXIT_FAILURE);
> 	}
> 	fprintf(stderr, "%s\n", s);
> 	exit(EXIT_FAILURE);
> }
>
> #define my_errPrintfBuffLen 256
> #define my_err(f, x...) \
> 	do { \
> 		char buff[my_errPrintfBuffLen]; \
> 		snprintf(buff, sizeof(buff) - 1, f, ## x); \
> 		buff[my_errPrintfBuffLen - 1] = 0; \
> 		my_err_s(buff); \
> 	} while (0)
>
> This makes any formatting be handled locally in the function that calls
> my_err, where the compiler can see the format string as a literal and
> check the format against the actual parameters used.  (There is, IIRC, a
> slight gcc'ism in the token pasting here to get the commas right even if
> my_err is called with no extra parameters.  But I expect clang supports
> the same feature.)

There are two GNUisms -- one is the comma pasting trick to handle zero
variable arguments, and the other is naming the variable arguments as x.

In fact, you don't need either as there is always one required
argument.  This is standard C99 and up:

  #define my_err(...) \
          do { \
             char buff[my_errPrintfBuffLen]; \
             snprintf(buff, sizeof(buff) - 1, __VA_ARGS__); \
             buff[my_errPrintfBuffLen - 1] = 0; \
             my_err_s(buff); \
          } while (0)

-- 
Ben.

[toc] | [prev] | [next] | [standalone]


#107810

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-07 13:35 +0200
Message-ID<oc7tcd$vi2$1@dont-email.me>
In reply to#107808
On 07/04/17 12:43, Ben Bacarisse wrote:
> David Brown <david.brown@hesbynett.no> writes:
> 
>> On 07/04/17 09:50, MehdiAmini wrote:
> <snip>
>>>     vfprintf(stderr,s,args);
> <snip>
>>> I get:
>>> warning: format string is not a string literal [-Wformat-nonliteral]
>>>     vfprintf(stderr,s,args);
> <snip>
> 
>> Another poster showed you how to disable the warning.
> <snip>
>> So it would be /much/ safer to have the function as:
>>
>> noreturn void my_err_s(const char * const s)
>> {
>> 	if ((s == NULL) || (*s == '\0')) {
>> 		fprintf(stderr, "Please provide a proper message for "
>> 			 "my_err function.\n");
>> 		exit(EXIT_FAILURE);
>> 	}
>> 	fprintf(stderr, "%s\n", s);
>> 	exit(EXIT_FAILURE);
>> }
>>
>> #define my_errPrintfBuffLen 256
>> #define my_err(f, x...) \
>> 	do { \
>> 		char buff[my_errPrintfBuffLen]; \
>> 		snprintf(buff, sizeof(buff) - 1, f, ## x); \
>> 		buff[my_errPrintfBuffLen - 1] = 0; \
>> 		my_err_s(buff); \
>> 	} while (0)
>>
>> This makes any formatting be handled locally in the function that calls
>> my_err, where the compiler can see the format string as a literal and
>> check the format against the actual parameters used.  (There is, IIRC, a
>> slight gcc'ism in the token pasting here to get the commas right even if
>> my_err is called with no extra parameters.  But I expect clang supports
>> the same feature.)
> 
> There are two GNUisms -- one is the comma pasting trick to handle zero
> variable arguments, and the other is naming the variable arguments as x.
> 
> In fact, you don't need either as there is always one required
> argument.  This is standard C99 and up:
> 
>   #define my_err(...) \
>           do { \
>              char buff[my_errPrintfBuffLen]; \
>              snprintf(buff, sizeof(buff) - 1, __VA_ARGS__); \
>              buff[my_errPrintfBuffLen - 1] = 0; \
>              my_err_s(buff); \
>           } while (0)
> 

Good point.  The macro was a modified copy of something from my own
code, which in this case already has plenty of GNUisms - a couple more
won't affect portability!  And while the OP here is using clang, which I
believe supports these extensions, removing them makes the code more
portable for other people.

[toc] | [prev] | [next] | [standalone]


#107813

FromBen Bacarisse <ben.usenet@bsb.me.uk>
Date2017-04-07 13:50 +0100
Message-ID<87mvbsv0tq.fsf@bsb.me.uk>
In reply to#107807
David Brown <david.brown@hesbynett.no> writes:
<snip>
> #define my_errPrintfBuffLen 256
> #define my_err(f, x...) \
> 	do { \
> 		char buff[my_errPrintfBuffLen]; \
> 		snprintf(buff, sizeof(buff) - 1, f, ## x); \
> 		buff[my_errPrintfBuffLen - 1] = 0; \

I was also going to mention this.  It seems a bit belt and braces.  Is
there any problem with

 		snprintf(buff, sizeof(buff), f, ## x); \

and no extra "buff[my_errPrintfBuffLen - 1] = 0;"?

Incidentally, mixing the use of my_errPrintfBuffLen and sizeof buff is
less the ideal.  I'd either use the defined constant only in the
declaration and sizeof buff - 1 in both other places, or, if I felt the
need to avoid confusing people with sizeof, I'd use the defined constant
everywhere.

> 		my_err_s(buff); \
> 	} while (0)

I might even just write

#define my_err(...) \
	do { \
		char buff[256]; \
		snprintf(buff, sizeof(buff), __VA_ARGS__); \
		my_err_s(buff); \
	} while (0)

<snip>
-- 
Ben.

[toc] | [prev] | [next] | [standalone]


#107816

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-07 15:54 +0200
Message-ID<oc85gk$s8g$1@dont-email.me>
In reply to#107813
On 07/04/17 14:50, Ben Bacarisse wrote:
> David Brown <david.brown@hesbynett.no> writes:
> <snip>
>> #define my_errPrintfBuffLen 256
>> #define my_err(f, x...) \
>> 	do { \
>> 		char buff[my_errPrintfBuffLen]; \
>> 		snprintf(buff, sizeof(buff) - 1, f, ## x); \
>> 		buff[my_errPrintfBuffLen - 1] = 0; \
> 
> I was also going to mention this.  It seems a bit belt and braces.  Is
> there any problem with
> 
>  		snprintf(buff, sizeof(buff), f, ## x); \
> 
> and no extra "buff[my_errPrintfBuffLen - 1] = 0;"?

Looks like you are right - snprintf guarantees to put a null character
at the end.  The history of this macro in my code stretches back to use
with older compilers from long ago, where I was less confident in the
accuracy of the C library implementation.  I can't remember if I
actually found a problem with a missing termination, or merely thought
it was a possibility.

> 
> Incidentally, mixing the use of my_errPrintfBuffLen and sizeof buff is
> less the ideal.  I'd either use the defined constant only in the
> declaration and sizeof buff - 1 in both other places, or, if I felt the
> need to avoid confusing people with sizeof, I'd use the defined constant
> everywhere.

Either method would work - I view the two expressions as synonymous and
interchangeable.  It might be a little different if the uses were
separated over a longer distance in code rather than being adjacent lines.

And I know that some people think it is nice to write "sizeof(buff)",
others prefer "sizeof buff".  I'd rather not argue too much about style
here.

> 
>> 		my_err_s(buff); \
>> 	} while (0)
> 
> I might even just write
> 
> #define my_err(...) \
> 	do { \
> 		char buff[256]; \
> 		snprintf(buff, sizeof(buff), __VA_ARGS__); \
> 		my_err_s(buff); \
> 	} while (0)
> 

That would be fine.


[toc] | [prev] | [next] | [standalone]


#107832

FromBen Bacarisse <ben.usenet@bsb.me.uk>
Date2017-04-07 19:35 +0100
Message-ID<87h920ukud.fsf@bsb.me.uk>
In reply to#107816
David Brown <david.brown@hesbynett.no> writes:

> On 07/04/17 14:50, Ben Bacarisse wrote:
>> David Brown <david.brown@hesbynett.no> writes:
>> <snip>
>>> #define my_errPrintfBuffLen 256
>>> #define my_err(f, x...) \
>>> 	do { \
>>> 		char buff[my_errPrintfBuffLen]; \
>>> 		snprintf(buff, sizeof(buff) - 1, f, ## x); \
>>> 		buff[my_errPrintfBuffLen - 1] = 0; \
>> 
>> I was also going to mention this.  It seems a bit belt and braces.  Is
>> there any problem with
>> 
>>  		snprintf(buff, sizeof(buff), f, ## x); \
>> 
>> and no extra "buff[my_errPrintfBuffLen - 1] = 0;"?
>
> Looks like you are right - snprintf guarantees to put a null character
> at the end.  The history of this macro in my code stretches back to use
> with older compilers from long ago, where I was less confident in the
> accuracy of the C library implementation.  I can't remember if I
> actually found a problem with a missing termination, or merely thought
> it was a possibility.

Yes, that was why I asked.  The sn* family predate the C99 standard but
I don't recall any that would not generate a string (unless the size is
zero of course).  But then my memory is not what it was, so I asked if
you had had reason to be suspicious.
 
>> Incidentally, mixing the use of my_errPrintfBuffLen and sizeof buff is
>> less the ideal.  I'd either use the defined constant only in the
>> declaration and sizeof buff - 1 in both other places, or, if I felt the
>> need to avoid confusing people with sizeof, I'd use the defined constant
>> everywhere.
>
> Either method would work - I view the two expressions as synonymous and
> interchangeable.  It might be a little different if the uses were
> separated over a longer distance in code rather than being adjacent lines.
>
> And I know that some people think it is nice to write "sizeof(buff)",
> others prefer "sizeof buff".  I'd rather not argue too much about style
> here.

Same here.  I wrote sizeof buff because that's how I write it.  I did
not intend to suggest there was any reason to change or re-hash that old
chestnut.  The mixing of the constant and sizeof is slightly more than
style, though, since there's a small cognitive cost to checking the
constant.  Even if defined just above, you need to check the spelling in
both usages just in case there's some other similar #define for
something else like my_logPrintBuffLen.

<snip>
-- 
Ben.

[toc] | [prev] | [next] | [standalone]


#107858

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-08 13:41 +0200
Message-ID<ocai4l$lc8$1@dont-email.me>
In reply to#107832
On 07/04/17 20:35, Ben Bacarisse wrote:
> David Brown <david.brown@hesbynett.no> writes:
>
>> On 07/04/17 14:50, Ben Bacarisse wrote:
>>> David Brown <david.brown@hesbynett.no> writes:
>>> <snip>
>>>> #define my_errPrintfBuffLen 256
>>>> #define my_err(f, x...) \
>>>> 	do { \
>>>> 		char buff[my_errPrintfBuffLen]; \
>>>> 		snprintf(buff, sizeof(buff) - 1, f, ## x); \
>>>> 		buff[my_errPrintfBuffLen - 1] = 0; \
>>>
>>> I was also going to mention this.  It seems a bit belt and braces.  Is
>>> there any problem with
>>>
>>>  		snprintf(buff, sizeof(buff), f, ## x); \
>>>
>>> and no extra "buff[my_errPrintfBuffLen - 1] = 0;"?
>>
>> Looks like you are right - snprintf guarantees to put a null character
>> at the end.  The history of this macro in my code stretches back to use
>> with older compilers from long ago, where I was less confident in the
>> accuracy of the C library implementation.  I can't remember if I
>> actually found a problem with a missing termination, or merely thought
>> it was a possibility.
>
> Yes, that was why I asked.  The sn* family predate the C99 standard but
> I don't recall any that would not generate a string (unless the size is
> zero of course).  But then my memory is not what it was, so I asked if
> you had had reason to be suspicious.

I can't remember the details of the tools I had at that time.  But in 
general, I have in the past used quite a number of compilers (and 
libraries) that intentionally or unintentionally (i.e., toolchain bugs) 
do not follow the rules of C.  Some of the fun ones include:

Floating point only working for local variables, not statically 
allocated variables.

Uninitialised data that is not zeroed before main().

"const" being treated as meaning "in flash", and using different 
assembly instructions for access than ram data - thus casting a "char *" 
pointer to a "const char *" pointer will break the program.

Integer promotion rules aimed at being "helpful" to the programmer, 
rather than following the standards.

And of course, lots of bugs in compilers and libraries.  Many of the 
tools I have used have been small ones for odd microcontrollers - they 
simply haven't had the levels of use and testing of "big" compilers.


But perhaps the reason I explicitly added a terminating 0 to that macro 
was that when I wrote it, I didn't know the details of snprintf well enough!


>
>>> Incidentally, mixing the use of my_errPrintfBuffLen and sizeof buff is
>>> less the ideal.  I'd either use the defined constant only in the
>>> declaration and sizeof buff - 1 in both other places, or, if I felt the
>>> need to avoid confusing people with sizeof, I'd use the defined constant
>>> everywhere.
>>
>> Either method would work - I view the two expressions as synonymous and
>> interchangeable.  It might be a little different if the uses were
>> separated over a longer distance in code rather than being adjacent lines.
>>
>> And I know that some people think it is nice to write "sizeof(buff)",
>> others prefer "sizeof buff".  I'd rather not argue too much about style
>> here.
>
> Same here.  I wrote sizeof buff because that's how I write it.  I did
> not intend to suggest there was any reason to change or re-hash that old
> chestnut.  The mixing of the constant and sizeof is slightly more than
> style, though, since there's a small cognitive cost to checking the
> constant.  Even if defined just above, you need to check the spelling in
> both usages just in case there's some other similar #define for
> something else like my_logPrintBuffLen.
>

Fair 'nuff.

[toc] | [prev] | [next] | [standalone]


#108013

Fromsupercat@casperkitty.com
Date2017-04-10 13:52 -0700
Message-ID<4f2b7ab6-2fa0-4fc9-aea2-7912adf068b5@googlegroups.com>
In reply to#107858
On Saturday, April 8, 2017 at 6:42:10 AM UTC-5, David Brown wrote:
> I can't remember the details of the tools I had at that time.  But in 
> general, I have in the past used quite a number of compilers (and 
> libraries) that intentionally or unintentionally (i.e., toolchain bugs) 
> do not follow the rules of C.  Some of the fun ones include:
> 
> Floating point only working for local variables, not statically 
> allocated variables.

I can't think of any implementations with that restriction, though I recall
one that didn't support floating-point types at all.  It might possibly have
allowed floating-point computations in constant expressions, but if it did I
never used that feature.  I can't think why only local variables could use
floating-point, though.

> Uninitialised data that is not zeroed before main().

For some embedded systems, it's essential to have some storage which is
not zeroed before main, since various events may cause the system to
reset and re-execute main() from scratch but with the contents of memory
undisturbed.  If a controller has a "power-on" hardware flag which gets
set by hardware whenever the voltage falls below a safe operational level
and can only be cleared by software when voltage is adequate for operation,
software can use that flag to manually initialize things that need to be
initialized.

The Standard regards as implementation-defined the nature and means by which
an embedded program starts operation.  It allows systems to define alternate
function signatures and startup patterns; I don't know whether a conforming
implementation could refrain from initializing global variables before it
called "int main(void)", but I think it document the behavior of "void main()"
as being similar to "int main(void)" but without zero-initialization.

> "const" being treated as meaning "in flash", and using different 
> assembly instructions for access than ram data - thus casting a "char *" 
> pointer to a "const char *" pointer will break the program.

I somewhat prefer the approach of using different qualifiers for things which
are guaranteed to be in RAM, guaranteed to be in code space, or might be in
either, but using "const" could be considered a practical approach even though
it is non-conforming.

> Integer promotion rules aimed at being "helpful" to the programmer, 
> rather than following the standards.

The Standard allows implementations some flexibility to be helpful with
regard to signed integers.  Unsigned integer types have rigidly-defined
behavior.

[toc] | [prev] | [next] | [standalone]


#108020

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-11 00:18 +0200
Message-ID<och05v$le7$1@dont-email.me>
In reply to#108013
On 10/04/17 22:52, supercat@casperkitty.com wrote:
> On Saturday, April 8, 2017 at 6:42:10 AM UTC-5, David Brown wrote:
>> I can't remember the details of the tools I had at that time.  But in
>> general, I have in the past used quite a number of compilers (and
>> libraries) that intentionally or unintentionally (i.e., toolchain bugs)
>> do not follow the rules of C.  Some of the fun ones include:
>>
>> Floating point only working for local variables, not statically
>> allocated variables.
>
> I can't think of any implementations with that restriction, though I recall
> one that didn't support floating-point types at all.  It might possibly have
> allowed floating-point computations in constant expressions, but if it did I
> never used that feature.  I can't think why only local variables could use
> floating-point, though.

It is highly unlikely that anyone would guess that situation - it was an 
odd one, due mainly to issues with the hardware on the board that meant 
access to external memory had problems, while the stack and local 
variables were on the small internal memory on the device.

>
>> Uninitialised data that is not zeroed before main().
>
> For some embedded systems, it's essential to have some storage which is
> not zeroed before main, since various events may cause the system to
> reset and re-execute main() from scratch but with the contents of memory
> undisturbed.  If a controller has a "power-on" hardware flag which gets
> set by hardware whenever the voltage falls below a safe operational level
> and can only be cleared by software when voltage is adequate for operation,
> software can use that flag to manually initialize things that need to be
> initialized.

If you are relying on the state of memory being preserved at the start 
of main(), then you are working outside of standard C.  There are three 
common ways to deal with that.  One is to use named sections for "no 
initialisation" data areas for the variables of interest.  Another is to 
fake that by using manually assigned addresses to ram that is outside of 
the areas used by C.  And a third is to put the relevant C code in 
sections or hooks for execution before the C runtime library clears the 
bss and sets up the data section.  These are, of course, all 
implementation-specific - but they will be clear and reliable methods.

There are some compiler manufactures (such as Texas Instruments) that 
seem to think it is a good idea not to zero out uninitialised data (the 
bss section) before main().  Their idea is that this makes startup 
faster, and that because their hardware watchdogs are often enabled out 
of reset (a questionable idea at best), they don't zero the bss to avoid 
a risk of the watchdog triggering before main() starts.  It is a silly 
idea, and has lead to much wailing and gnashing of teeth for developers 
who find their programs behaving differently from run to run until they 
learn of this "feature".

>
> The Standard regards as implementation-defined the nature and means by which
> an embedded program starts operation.  It allows systems to define alternate
> function signatures and startup patterns; I don't know whether a conforming
> implementation could refrain from initializing global variables before it
> called "int main(void)", but I think it document the behavior of "void main()"
> as being similar to "int main(void)" but without zero-initialization.

They /could/ document it - but it would then not be C, because C 
requires this zero initialisation (5.1.2, 6.7.9 p 10).  In at least one 
of TI's compiler manuals, I did see that it was documented - as a tiny 
footnote, deep within the middle of the manual, with a comment that the 
behaviour broke with the C standards.

>
>> "const" being treated as meaning "in flash", and using different
>> assembly instructions for access than ram data - thus casting a "char *"
>> pointer to a "const char *" pointer will break the program.
>
> I somewhat prefer the approach of using different qualifiers for things which
> are guaranteed to be in RAM, guaranteed to be in code space, or might be in
> either, but using "const" could be considered a practical approach even though
> it is non-conforming.

It was a terrible approach, because it silently broke code that used the 
good programming practice of using const pointers when a function does 
not change the data pointed at.  It is far better to use explicit 
extensions, such as a "flash" keyword, for that purpose.

>
>> Integer promotion rules aimed at being "helpful" to the programmer,
>> rather than following the standards.
>
> The Standard allows implementations some flexibility to be helpful with
> regard to signed integers.  Unsigned integer types have rigidly-defined
> behavior.
>

No, it does not allow such flexibility.  If you have a uint8_t or an 
int8_t, and you perform arithmetic, it is to be promoted to "int" before 
carrying out the operation.  A compiler that does not do that 
(logically, at least - it does not actually have to do the 16+ bit 
operations if only parts of the results are used) is a broken compiler 
that causes confusion and means code that should be portable and 
testable on different targets, no longer works.

In most cases, these things are done in the mistaken belief that the 
compiler writer is helping users, or making the tool easy for beginners, 
or making a more "natural" language for the particular target.  But it 
means that people who know what they are doing, are going to have 
trouble because the compiler does not work the way they expect.  And 
people who /don't/ know what they are doing, are going to have trouble - 
because they will refer to books, websites, and expert opinions which 
are all incompatible with the "helpful" compiler.

[toc] | [prev] | [next] | [standalone]


#108022

Fromsupercat@casperkitty.com
Date2017-04-10 15:43 -0700
Message-ID<2937e657-8cbe-40ad-a526-978b29bd9247@googlegroups.com>
In reply to#108020
On Monday, April 10, 2017 at 5:18:39 PM UTC-5, David Brown wrote:
> There are some compiler manufactures (such as Texas Instruments) that 
> seem to think it is a good idea not to zero out uninitialised data (the 
> bss section) before main().  Their idea is that this makes startup 
> faster, and that because their hardware watchdogs are often enabled out 
> of reset (a questionable idea at best), they don't zero the bss to avoid 
> a risk of the watchdog triggering before main() starts.  It is a silly 
> idea, and has lead to much wailing and gnashing of teeth for developers 
> who find their programs behaving differently from run to run until they 
> learn of this "feature".

The Keil/ARM approach to that issue is to call a function called SystemInit
before any static-duration variables are initialized, and then initialize
static-duration variables and call main() after that.  I would expect that
it would be possible to have SystemInit invoke the main program loop itself
without returning in cases where initialization was known to have happened
previously and should not be repeated, but my preferred approach is to use
separate sections for things that have to survive a reset.

> > The Standard regards as implementation-defined the nature and means by which
> > an embedded program starts operation.  It allows systems to define alternate
> > function signatures and startup patterns; I don't know whether a conforming
> > implementation could refrain from initializing global variables before it
> > called "int main(void)", but I think it document the behavior of "void main()"
> > as being similar to "int main(void)" but without zero-initialization.
> 
> They /could/ document it - but it would then not be C, because C 
> requires this zero initialisation (5.1.2, 6.7.9 p 10).  In at least one 
> of TI's compiler manuals, I did see that it was documented - as a tiny 
> footnote, deep within the middle of the manual, with a comment that the 
> behaviour broke with the C standards.

I don't think the Standard allows compilers to regard as Undefined Behavior
the use of any entry-function signature other than those explicitly described.
If an implementation that didn't describe any behavior for "void main()"
would be allowed to treat it as UB, I see no reason that it shouldn't be
allowed to describe a useful behavior that differs from "int main()".

> It was a terrible approach, because it silently broke code that used the 
> good programming practice of using const pointers when a function does 
> not change the data pointed at.  It is far better to use explicit 
> extensions, such as a "flash" keyword, for that purpose.

The "const" pointers were treated as a "universal" pointer type that could
access things in either RAM or flash/ROM.  A pointer type for things that
were known to be in flash would have improved efficiency in some cases,
though access through flash was generally slow enough that the extra time
to check whether a pointer was a RAM address wasn't a problem.  The treatment
meant that a *const* was incompatible with a **, and meant that code which
needlessly applied "const" to pointers would run much more slowly than it
should, but maintained correct behavior in most cases.

Maintaining compatibility with 100% of code would require using two-byte
pointers for everything and generating a function call for every pointer
access.  While requiring that code mark everyplace where a one-byte pointer
should be used might have worked, it would have required a lot of code
markup.

> >> Integer promotion rules aimed at being "helpful" to the programmer,
> >> rather than following the standards.
> >
> > The Standard allows implementations some flexibility to be helpful with
> > regard to signed integers.  Unsigned integer types have rigidly-defined
> > behavior.
> 
> No, it does not allow such flexibility.  If you have a uint8_t or an 
> int8_t, and you perform arithmetic, it is to be promoted to "int" before 
> carrying out the operation.  A compiler that does not do that 
> (logically, at least - it does not actually have to do the 16+ bit 
> operations if only parts of the results are used) is a broken compiler 
> that causes confusion and means code that should be portable and 
> testable on different targets, no longer works.

A compiler could just as well promote to an even longer type at its
convenience, except in contexts like "sizeof", since operations on the
longer type would behave identically to "int" in all cases where the
Standard would impose any requirements upon the latter.  Further, nothing
in the Standard would forbid a compiler where "int" is 32 bits from treating

    unsigned mul(unsigned short x, unsigned short y) { return x*y; }

as equivalent to:

    unsigned mul(unsigned short x, unsigned short y) { return 1U*x*y; }

Indeed, looking at the C89 rationale, I think the authors of the Standard
would have been shocked at the notion that a compiler for two's-complement
silent-wraparound hardware should do otherwise except perhaps during a
pedantic sanitizing build.

[toc] | [prev] | [next] | [standalone]


#108023

FromKeith Thompson <kst-u@mib.org>
Date2017-04-10 16:10 -0700
Message-ID<ln37dfx3jf.fsf@kst-u.example.com>
In reply to#108022
supercat@casperkitty.com writes:
[...]
> I don't think the Standard allows compilers to regard as Undefined Behavior
> the use of any entry-function signature other than those explicitly described.

I can't think of any interpretation of that statement that is both
reasonable and correct.  Can you rephrase it, with careful consideration
to what the phrase "undefined behavior" actually means (N1570 3.4.3)?
If you're using the phrase "undefined behavior" in a sense other than
that defined by the standard, please don't bother explaining.

> If an implementation that didn't describe any behavior for "void main()"
> would be allowed to treat it as UB, I see no reason that it shouldn't be
> allowed to describe a useful behavior that differs from "int main()".

If I understand that correctly, I agree.  Using prototypes rather
than old-style declarations for greater clarity, if an implementation
doesn't define the behavior of `void main(void)`, then its behavior
is undefined -- and always, the actual behavior might be something
useful, whether it's documented/defined or not.  An implementation
is free to define the behavior of `void main(void)` in any way it
likes (as long as it doesn't contradict the rest of the standard)
(5.1.2.2.1p1), and that behavior needn't match the behavior of
`int main(void)`.

(Where "the behavior of `void main(void)`" is verbal shorthand for "the
behavior of a program that defines `void main(void) { /* ... */ }`.)

[...]

-- 
Keith Thompson (The_Other_Keith) kst-u@mib.org  <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"

[toc] | [prev] | [next] | [standalone]


#108025

Fromsupercat@casperkitty.com
Date2017-04-10 16:26 -0700
Message-ID<13ec800d-d0e6-400e-a054-6957d3d9da03@googlegroups.com>
In reply to#108023
On Monday, April 10, 2017 at 6:10:19 PM UTC-5, Keith Thompson wrote:
> supercat writes:
> > If an implementation that didn't describe any behavior for "void main()"
> > would be allowed to treat it as UB, I see no reason that it shouldn't be
> > allowed to describe a useful behavior that differs from "int main()".
> 
> If I understand that correctly, I agree.  Using prototypes rather
> than old-style declarations for greater clarity, if an implementation
> doesn't define the behavior of `void main(void)`, then its behavior
> is undefined -- and always, the actual behavior might be something
> useful, whether it's documented/defined or not.  An implementation
> is free to define the behavior of `void main(void)` in any way it
> likes (as long as it doesn't contradict the rest of the standard)
> (5.1.2.2.1p1), and that behavior needn't match the behavior of
> `int main(void)`.
> 
> (Where "the behavior of `void main(void)`" is verbal shorthand for "the
> behavior of a program that defines `void main(void) { /* ... */ }`.)

That's my thinking.  And I think the initialization behavior would only be
required before a call to one of the forms of "int main()" that would be
described by the Standard; I certainly don't think the authors of the
Standard wanted to forbid the approach used by the Keil compiler (with its
call to SystemInit() before variable initialization), and if SystemInit never
returns, the implementation won't ever call main().

[toc] | [prev] | [next] | [standalone]


#108038

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-11 11:33 +0200
Message-ID<oci7n4$l8l$1@dont-email.me>
In reply to#108022
On 11/04/17 00:43, supercat@casperkitty.com wrote:
> On Monday, April 10, 2017 at 5:18:39 PM UTC-5, David Brown wrote:
>> There are some compiler manufactures (such as Texas Instruments) that
>> seem to think it is a good idea not to zero out uninitialised data (the
>> bss section) before main().  Their idea is that this makes startup
>> faster, and that because their hardware watchdogs are often enabled out
>> of reset (a questionable idea at best), they don't zero the bss to avoid
>> a risk of the watchdog triggering before main() starts.  It is a silly
>> idea, and has lead to much wailing and gnashing of teeth for developers
>> who find their programs behaving differently from run to run until they
>> learn of this "feature".
>
> The Keil/ARM approach to that issue is to call a function called SystemInit
> before any static-duration variables are initialized, and then initialize
> static-duration variables and call main() after that.  I would expect that
> it would be possible to have SystemInit invoke the main program loop itself
> without returning in cases where initialization was known to have happened
> previously and should not be repeated, but my preferred approach is to use
> separate sections for things that have to survive a reset.
>

Such "SystemInit" hooks are usually used for configuring external 
memory, setting up clocks, handling watchdog setup, etc.  They /can/ be 
used for dealing with data that needs preserved over restarts.  But I 
agree that separate sections are usually the most convenient method.

>>> The Standard regards as implementation-defined the nature and means by which
>>> an embedded program starts operation.  It allows systems to define alternate
>>> function signatures and startup patterns; I don't know whether a conforming
>>> implementation could refrain from initializing global variables before it
>>> called "int main(void)", but I think it document the behavior of "void main()"
>>> as being similar to "int main(void)" but without zero-initialization.
>>
>> They /could/ document it - but it would then not be C, because C
>> requires this zero initialisation (5.1.2, 6.7.9 p 10).  In at least one
>> of TI's compiler manuals, I did see that it was documented - as a tiny
>> footnote, deep within the middle of the manual, with a comment that the
>> behaviour broke with the C standards.
>
> I don't think the Standard allows compilers to regard as Undefined Behavior
> the use of any entry-function signature other than those explicitly described.
> If an implementation that didn't describe any behavior for "void main()"
> would be allowed to treat it as UB, I see no reason that it shouldn't be
> allowed to describe a useful behavior that differs from "int main()".
>

I can't figure out what you are trying to say here.  We are not talking 
about undefined behaviour here - we are talking about a non-conforming 
implementation of the compiler.

>> It was a terrible approach, because it silently broke code that used the
>> good programming practice of using const pointers when a function does
>> not change the data pointed at.  It is far better to use explicit
>> extensions, such as a "flash" keyword, for that purpose.
>
> The "const" pointers were treated as a "universal" pointer type that could
> access things in either RAM or flash/ROM.

C does not make a distinction between RAM or ROM.  /All/ pointers in C 
to a given type are "universal" pointers.  A const pointer is a pointer 
which the /programmer/ promises he will not use to modify data.  Data 
that is defined as const is stronger - there the compiler /knows/ that 
the data will never be modified, and is allowed to place it in read-only 
memory.

Of course, a compiler implementation can provide extensions beyond this 
model - and perhaps limitations if necessary in order to keep an 
efficient implementation.  But a compiler implementation may not usurp 
normal standard C keywords and usage for such different behaviour.

In this particular case, the microcontroller in question was an AVR. 
This is an 8-bit device, and it uses different cpu instructions to 
access memory in RAM from memory in Flash.  The old compiler (I omit the 
name to protect the guilty) was trying to make it convenient for casual 
users to have data such as tables or strings in flash.  In contrast, the 
standard compiler for this chip is gcc which uses normal semantics and 
normal standard C.  This means that if you write "const int x = 1234;" 
and take the address of x, then x will be placed in RAM - even though it 
never changes.  This is necessary to make pointers work properly, as 
normal C.  The compiler provides extensions to allow you to put data 
specifically in flash and access it directly (including a type of 
universal pointer that can access anything).

>  A pointer type for things that
> were known to be in flash would have improved efficiency in some cases,
> though access through flash was generally slow enough that the extra time
> to check whether a pointer was a RAM address wasn't a problem.  The treatment
> meant that a *const* was incompatible with a **, and meant that code which
> needlessly applied "const" to pointers would run much more slowly than it
> should, but maintained correct behavior in most cases.
>
> Maintaining compatibility with 100% of code would require using two-byte
> pointers for everything and generating a function call for every pointer
> access.  While requiring that code mark everyplace where a one-byte pointer
> should be used might have worked, it would have required a lot of code
> markup.
>
>>>> Integer promotion rules aimed at being "helpful" to the programmer,
>>>> rather than following the standards.
>>>
>>> The Standard allows implementations some flexibility to be helpful with
>>> regard to signed integers.  Unsigned integer types have rigidly-defined
>>> behavior.
>>
>> No, it does not allow such flexibility.  If you have a uint8_t or an
>> int8_t, and you perform arithmetic, it is to be promoted to "int" before
>> carrying out the operation.  A compiler that does not do that
>> (logically, at least - it does not actually have to do the 16+ bit
>> operations if only parts of the results are used) is a broken compiler
>> that causes confusion and means code that should be portable and
>> testable on different targets, no longer works.
>
> A compiler could just as well promote to an even longer type at its
> convenience, except in contexts like "sizeof", since operations on the
> longer type would behave identically to "int" in all cases where the
> Standard would impose any requirements upon the latter.  Further, nothing
> in the Standard would forbid a compiler where "int" is 32 bits from treating
>
>     unsigned mul(unsigned short x, unsigned short y) { return x*y; }
>
> as equivalent to:
>
>     unsigned mul(unsigned short x, unsigned short y) { return 1U*x*y; }
>
> Indeed, looking at the C89 rationale, I think the authors of the Standard
> would have been shocked at the notion that a compiler for two's-complement
> silent-wraparound hardware should do otherwise except perhaps during a
> pedantic sanitizing build.
>

You don't need to repeat your favourite bugbear yet again - it is not 
relevant here, any more than it is most of the times you bring it up.

[toc] | [prev] | [next] | [standalone]


#108042

Fromsupercat@casperkitty.com
Date2017-04-11 08:37 -0700
Message-ID<aa3e8f39-85a0-4c75-9adc-bc79d54005f5@googlegroups.com>
In reply to#108038
On Tuesday, April 11, 2017 at 4:33:26 AM UTC-5, David Brown wrote:
> On 11/04/17 00:43, supercat wrote:
> > On Monday, April 10, 2017 at 5:18:39 PM UTC-5, David Brown wrote:
> > I don't think the Standard allows compilers to regard as Undefined Behavior
> > the use of any entry-function signature other than those explicitly described.
> > If an implementation that didn't describe any behavior for "void main()"
> > would be allowed to treat it as UB, I see no reason that it shouldn't be
> > allowed to describe a useful behavior that differs from "int main()".
> >
> 
> I can't figure out what you are trying to say here.  We are not talking 
> about undefined behaviour here - we are talking about a non-conforming 
> implementation of the compiler.

If some compiler X doesn't document any particular behavior for "void main()"
but such code happens to behave like "int main()" but without variable
initialization, such behavior would be conforming since use of "void main()"
would be UB, making all possible actions conforming.

If some compiler Y were identical to X except that it *documented* that
"void main()" would work as it does in compiler X, then "void main()" would
not be UB on compiler Y, but I see no reason that compiler Y shouldn't be
conforming.

> > The "const" pointers were treated as a "universal" pointer type that could
> > access things in either RAM or flash/ROM.
> 
> C does not make a distinction between RAM or ROM.  /All/ pointers in C 
> to a given type are "universal" pointers.  A const pointer is a pointer 
> which the /programmer/ promises he will not use to modify data.  Data 
> that is defined as const is stronger - there the compiler /knows/ that 
> the data will never be modified, and is allowed to place it in read-only 
> memory.

I'm well aware of that.  The question is whether it's better to have a
compiler:

  1. default to RAM-only pointers and require that all objects fit in
     RAM unless they have a special qualifier that makes them only
     accessible via specially-qualified pointers

  2. default to universal pointers and require that any code that needs
     to perform well with data in RAM use qualifiers to allow that, or
     have the default behavior in the absence of vendor-specific
     qualifiers depend upon whether things are "const" qualified.  If I
     were designing a compiler, I'd be inclined to allow command line
     settings or #pragma directives select among such behaviors, since
     there are situations where each could be most useful, but the
     non-conforming option is probably the one which would allow the
     largest amount of code to work efficiently without modification.

> Of course, a compiler implementation can provide extensions beyond this 
> model - and perhaps limitations if necessary in order to keep an 
> efficient implementation.  But a compiler implementation may not usurp 
> normal standard C keywords and usage for such different behaviour.

Compilers can do whatever they want.  Such behavior may make them non-
conforming, but in some situations a non-conforming compiler might be more
useful than any fully-conforming compiler ever could be.  For example, on
an 8-bit processor, a compiler that has a single 32-bit floating-point type
may be more useful than one which can't pass floating-point values to
variadic functions without bundling 48-bit-or-larger math code.  If code
never needs anything beyond standard float precision, the extra machine code
for larger types would represent a waste of time and space.

> In this particular case, the microcontroller in question was an AVR. 
> This is an 8-bit device, and it uses different cpu instructions to 
> access memory in RAM from memory in Flash.  The old compiler (I omit the 
> name to protect the guilty) was trying to make it convenient for casual 
> users to have data such as tables or strings in flash.  In contrast, the 
> standard compiler for this chip is gcc which uses normal semantics and 
> normal standard C.  This means that if you write "const int x = 1234;" 
> and take the address of x, then x will be placed in RAM - even though it 
> never changes.  This is necessary to make pointers work properly, as 
> normal C.  The compiler provides extensions to allow you to put data 
> specifically in flash and access it directly (including a type of 
> universal pointer that can access anything).

That's certainly a reasonable approach, and a quality compiler should
probably offer it as an option, but for many purposes a non-conforming
option that works like other compilers do may be more useful.

> >  A pointer type for things that
> > were known to be in flash would have improved efficiency in some cases,
> > though access through flash was generally slow enough that the extra time
> > to check whether a pointer was a RAM address wasn't a problem.  The treatment
> > meant that a *const* was incompatible with a **, and meant that code which
> > needlessly applied "const" to pointers would run much more slowly than it
> > should, but maintained correct behavior in most cases.
> >
> > Maintaining compatibility with 100% of code would require using two-byte
> > pointers for everything and generating a function call for every pointer
> > access.  While requiring that code mark everyplace where a one-byte pointer
> > should be used might have worked, it would have required a lot of code
> > markup.
> >
> >>>> Integer promotion rules aimed at being "helpful" to the programmer,
> >>>> rather than following the standards.
> >>>
> >>> The Standard allows implementations some flexibility to be helpful with
> >>> regard to signed integers.  Unsigned integer types have rigidly-defined
> >>> behavior.
> >>
> >> No, it does not allow such flexibility.  If you have a uint8_t or an
> >> int8_t, and you perform arithmetic, it is to be promoted to "int" before
> >> carrying out the operation.  A compiler that does not do that
> >> (logically, at least - it does not actually have to do the 16+ bit
> >> operations if only parts of the results are used) is a broken compiler
> >> that causes confusion and means code that should be portable and
> >> testable on different targets, no longer works.
> >
> > A compiler could just as well promote to an even longer type at its
> > convenience, except in contexts like "sizeof", since operations on the
> > longer type would behave identically to "int" in all cases where the
> > Standard would impose any requirements upon the latter....

> You don't need to repeat your favourite bugbear yet again - it is not 
> relevant here, any more than it is most of the times you bring it up.

What about the other point (requoted above): given code like:

    int64_t mul_and_add(int32_t x, int32_t y)
    {
      return x*y;
    }

When generating code for a 32-bit processor that includes a 32x32->64
multiply instruction, the Standard would allow a compiler to, at its
leisure, either perform the multiply as 32x32->32 and sign-extend, or
else perform a 32x32->64 computation and return the result as-is?  I
don't know of any compilers that would actually promise the latter,
but the Standard would certainly allow a compiler to do so.  On a
processor which uses 32-bit "int" on a 64-bit processor, such a promise
would be rather cheap, though code would have to avoid expressions that
mixed uint32_t with int32_t or smaller types to avoid icky semantics.

[toc] | [prev] | [next] | [standalone]


#108083

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-13 00:20 +0200
Message-ID<ocm91l$nkc$1@dont-email.me>
In reply to#108042
On 11/04/17 17:37, supercat@casperkitty.com wrote:
> On Tuesday, April 11, 2017 at 4:33:26 AM UTC-5, David Brown wrote:
>> On 11/04/17 00:43, supercat wrote:
>>> On Monday, April 10, 2017 at 5:18:39 PM UTC-5, David Brown wrote:
>>> I don't think the Standard allows compilers to regard as Undefined Behavior
>>> the use of any entry-function signature other than those explicitly described.
>>> If an implementation that didn't describe any behavior for "void main()"
>>> would be allowed to treat it as UB, I see no reason that it shouldn't be
>>> allowed to describe a useful behavior that differs from "int main()".
>>>
>>
>> I can't figure out what you are trying to say here.  We are not talking
>> about undefined behaviour here - we are talking about a non-conforming
>> implementation of the compiler.
>
> If some compiler X doesn't document any particular behavior for "void main()"
> but such code happens to behave like "int main()" but without variable
> initialization, such behavior would be conforming since use of "void main()"
> would be UB, making all possible actions conforming.
>
> If some compiler Y were identical to X except that it *documented* that
> "void main()" would work as it does in compiler X, then "void main()" would
> not be UB on compiler Y, but I see no reason that compiler Y shouldn't be
> conforming.

You are still making no sense.  The implementation's way of handling 
main() cannot be undefined behaviour - it is defined by the 
implementation.  It might not conform to the C standards, but it can 
never be undefined behaviour.

>
>>> The "const" pointers were treated as a "universal" pointer type that could
>>> access things in either RAM or flash/ROM.
>>
>> C does not make a distinction between RAM or ROM.  /All/ pointers in C
>> to a given type are "universal" pointers.  A const pointer is a pointer
>> which the /programmer/ promises he will not use to modify data.  Data
>> that is defined as const is stronger - there the compiler /knows/ that
>> the data will never be modified, and is allowed to place it in read-only
>> memory.
>
> I'm well aware of that.  The question is whether it's better to have a
> compiler:
>
>   1. default to RAM-only pointers and require that all objects fit in
>      RAM unless they have a special qualifier that makes them only
>      accessible via specially-qualified pointers
>
>   2. default to universal pointers and require that any code that needs
>      to perform well with data in RAM use qualifiers to allow that, or
>      have the default behavior in the absence of vendor-specific
>      qualifiers depend upon whether things are "const" qualified.  If I
>      were designing a compiler, I'd be inclined to allow command line
>      settings or #pragma directives select among such behaviors, since
>      there are situations where each could be most useful, but the
>      non-conforming option is probably the one which would allow the
>      largest amount of code to work efficiently without modification.
>

/You/ might be inclined to allow weird behaviour, but we already know 
you have strange ideas about what C is, was or should be.

The question /really/ is, is it better to have a compiler:

1. Accept normal C and work the way everyone expects a C compiler to 
work, allowing good and safe code to work correctly.  For some bits of 
code, extension keywords or other implementation-specific features must 
be used for optimal efficiency due to limitations of the target 
architecture.

or

2. Change important keywords of C to work in a substantially different 
way, breaking correct code, because you think it would be easier for 
beginners to understand this non-standard variant of C.

>> Of course, a compiler implementation can provide extensions beyond this
>> model - and perhaps limitations if necessary in order to keep an
>> efficient implementation.  But a compiler implementation may not usurp
>> normal standard C keywords and usage for such different behaviour.
>
> Compilers can do whatever they want.  Such behavior may make them non-
> conforming, but in some situations a non-conforming compiler might be more
> useful than any fully-conforming compiler ever could be.  For example, on
> an 8-bit processor, a compiler that has a single 32-bit floating-point type
> may be more useful than one which can't pass floating-point values to
> variadic functions without bundling 48-bit-or-larger math code.  If code
> never needs anything beyond standard float precision, the extra machine code
> for larger types would represent a waste of time and space.
>

I agree that for some targets, it can be better to make some breakage to 
C standards conformity.  But that should be kept to the absolute 
minimum, be well documented, not break working code silently, and 
preferably require a command-line switch to activate it or give warnings 
about the odd behaviour.  It is reasonable, for example, for a C 
implementation on a small 8-bit micro to make "double" limited to 32 
bits.  It is /not/ reasonable to change the meaning of a keyword like 
"const" to mean something subtly different, or to skip the zeroing of 
uninitialised data.

>> In this particular case, the microcontroller in question was an AVR.
>> This is an 8-bit device, and it uses different cpu instructions to
>> access memory in RAM from memory in Flash.  The old compiler (I omit the
>> name to protect the guilty) was trying to make it convenient for casual
>> users to have data such as tables or strings in flash.  In contrast, the
>> standard compiler for this chip is gcc which uses normal semantics and
>> normal standard C.  This means that if you write "const int x = 1234;"
>> and take the address of x, then x will be placed in RAM - even though it
>> never changes.  This is necessary to make pointers work properly, as
>> normal C.  The compiler provides extensions to allow you to put data
>> specifically in flash and access it directly (including a type of
>> universal pointer that can access anything).
>
> That's certainly a reasonable approach, and a quality compiler should
> probably offer it as an option, but for many purposes a non-conforming
> option that works like other compilers do may be more useful.
>

No, the "break the const keyword" approach is not more useful.

>>>  A pointer type for things that
>>> were known to be in flash would have improved efficiency in some cases,
>>> though access through flash was generally slow enough that the extra time
>>> to check whether a pointer was a RAM address wasn't a problem.  The treatment
>>> meant that a *const* was incompatible with a **, and meant that code which
>>> needlessly applied "const" to pointers would run much more slowly than it
>>> should, but maintained correct behavior in most cases.
>>>
>>> Maintaining compatibility with 100% of code would require using two-byte
>>> pointers for everything and generating a function call for every pointer
>>> access.  While requiring that code mark everyplace where a one-byte pointer
>>> should be used might have worked, it would have required a lot of code
>>> markup.
>>>
>>>>>> Integer promotion rules aimed at being "helpful" to the programmer,
>>>>>> rather than following the standards.
>>>>>
>>>>> The Standard allows implementations some flexibility to be helpful with
>>>>> regard to signed integers.  Unsigned integer types have rigidly-defined
>>>>> behavior.
>>>>
>>>> No, it does not allow such flexibility.  If you have a uint8_t or an
>>>> int8_t, and you perform arithmetic, it is to be promoted to "int" before
>>>> carrying out the operation.  A compiler that does not do that
>>>> (logically, at least - it does not actually have to do the 16+ bit
>>>> operations if only parts of the results are used) is a broken compiler
>>>> that causes confusion and means code that should be portable and
>>>> testable on different targets, no longer works.
>>>
>>> A compiler could just as well promote to an even longer type at its
>>> convenience, except in contexts like "sizeof", since operations on the
>>> longer type would behave identically to "int" in all cases where the
>>> Standard would impose any requirements upon the latter....
>
>> You don't need to repeat your favourite bugbear yet again - it is not
>> relevant here, any more than it is most of the times you bring it up.
>

<snip the needless repetition>

[toc] | [prev] | [next] | [standalone]


#108087

Fromsupercat@casperkitty.com
Date2017-04-12 15:55 -0700
Message-ID<5ef4318f-a4f1-4be1-8e9e-bcf6071e6439@googlegroups.com>
In reply to#108083
On Wednesday, April 12, 2017 at 5:20:37 PM UTC-5, David Brown wrote:
> On 11/04/17 17:37, supercat wrote:
> > If some compiler Y were identical to X except that it *documented* that
> > "void main()" would work as it does in compiler X, then "void main()" would
> > not be UB on compiler Y, but I see no reason that compiler Y shouldn't be
> > conforming.
> 
> You are still making no sense.  The implementation's way of handling 
> main() cannot be undefined behaviour - it is defined by the 
> implementation.  It might not conform to the C standards, but it can 
> never be undefined behaviour.

The Standard defines the behavior for "int main(void)".  Where does it define
any behavior for "void main(void)"?

> The question /really/ is, is it better to have a compiler:
> 
> 1. Accept normal C and work the way everyone expects a C compiler to 
> work, allowing good and safe code to work correctly.  For some bits of 
> code, extension keywords or other implementation-specific features must 
> be used for optimal efficiency due to limitations of the target 
> architecture.
> 
> or
> 
> 2. Change important keywords of C to work in a substantially different 
> way, breaking correct code, because you think it would be easier for 
> beginners to understand this non-standard variant of C.

No implementation will be able to run all correct code.  Each of the above
approaches will run some programs that the other would not.  If some
particular program will run usefully on the second approach, but would be
unable to run using the first because of resource constraints, then I would
suggest the second implementation would be more useful for the purpose of
running that program.

> I agree that for some targets, it can be better to make some breakage to 
> C standards conformity.  But that should be kept to the absolute 
> minimum, be well documented, not break working code silently, and 
> preferably require a command-line switch to activate it or give warnings 
> about the odd behaviour.  It is reasonable, for example, for a C 
> implementation on a small 8-bit micro to make "double" limited to 32 
> bits.  It is /not/ reasonable to change the meaning of a keyword like 
> "const" to mean something subtly different, or to skip the zeroing of 
> uninitialised data.

Most code won't need to convert a const-qualified pointer to a non-qualified
pointer and then later convert back to a const-qualified pointer and access
ROM/flash.  While a compiler should have an option to make all pointers use
the same format as const-qualified pointers, I would not expect that most
programs would work better with that option disabled.

> > That's certainly a reasonable approach, and a quality compiler should
> > probably offer it as an option, but for many purposes a non-conforming
> > option that works like other compilers do may be more useful.
> 
> No, the "break the const keyword" approach is not more useful.

If a program would work correctly with or with or without that option, but
be faster without, or if it would work correctly with that option but would
not fit in the available code space without it, in what way would that
option make the implementation more useful for purposes of running that
program?

If the Atmel can't use a const-qualified pointer to access RAM (I've not
used that platform, so I don't know if it can), that would be a horribly
broken abuse of the "const" keyword, but if "const"-qualified pointers
can access either while unqualified pointers are limited to RAM, most
programs would not be affected by an inability to round-trip const pointers
through unqualified pointers nor directly use unqualified pointers to
access ROM.

[toc] | [prev] | [next] | [standalone]


#108094

FromKeith Thompson <kst-u@mib.org>
Date2017-04-12 18:17 -0700
Message-ID<lnpoghumwg.fsf@kst-u.example.com>
In reply to#108087
supercat@casperkitty.com writes:
> On Wednesday, April 12, 2017 at 5:20:37 PM UTC-5, David Brown wrote:
>> On 11/04/17 17:37, supercat wrote:
>> > If some compiler Y were identical to X except that it *documented* that
>> > "void main()" would work as it does in compiler X, then "void main()" would
>> > not be UB on compiler Y, but I see no reason that compiler Y shouldn't be
>> > conforming.
>> 
>> You are still making no sense.  The implementation's way of handling 
>> main() cannot be undefined behaviour - it is defined by the 
>> implementation.  It might not conform to the C standards, but it can 
>> never be undefined behaviour.
>
> The Standard defines the behavior for "int main(void)".  Where does it define
> any behavior for "void main(void)"?

If "void main(void)" is documented as "some other implementation-defined
manner" as described in 5.1.2.2.1p1, its behavior (other than its
termination status) is defined by the entire standard.

[...]

> No implementation will be able to run all correct code.  Each of the above
> approaches will run some programs that the other would not.  If some
> particular program will run usefully on the second approach, but would be
> unable to run using the first because of resource constraints, then I would
> suggest the second implementation would be more useful for the purpose of
> running that program.

Any acceptable implementation will be useful for more than the purpose
of running one specific program.

There are ways to define extensions without voilating the standard, such
as "#pragma".

[...]

-- 
Keith Thompson (The_Other_Keith) kst-u@mib.org  <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"

[toc] | [prev] | [next] | [standalone]


#108120

FromDavid Brown <david.brown@hesbynett.no>
Date2017-04-14 12:27 +0200
Message-ID<ocq815$4ud$1@dont-email.me>
In reply to#108087
On 13/04/17 00:55, supercat@casperkitty.com wrote:
> On Wednesday, April 12, 2017 at 5:20:37 PM UTC-5, David Brown wrote:
>> On 11/04/17 17:37, supercat wrote:
>>> If some compiler Y were identical to X except that it *documented* that
>>> "void main()" would work as it does in compiler X, then "void main()" would
>>> not be UB on compiler Y, but I see no reason that compiler Y shouldn't be
>>> conforming.
>>
>> You are still making no sense.  The implementation's way of handling
>> main() cannot be undefined behaviour - it is defined by the
>> implementation.  It might not conform to the C standards, but it can
>> never be undefined behaviour.
>
> The Standard defines the behavior for "int main(void)".  Where does it define
> any behavior for "void main(void)"?

I still don't get your hang-up about main() here, or your idea of the 
implementation being undefined behaviour.

The standards make it clear that program lifetime objects without 
explicit initialisation are zero'ed before the start of the program.  A 
freestanding C implementation can use whatever it likes as a start 
function - it can be "void main(void)", or "int reset(int)", or whatever 
it wants.  That is for the implementation to decide.  And if the 
programmer uses a form that is not specified by the implementation, then 
/that/ is undefined behaviour.

>
>> The question /really/ is, is it better to have a compiler:
>>
>> 1. Accept normal C and work the way everyone expects a C compiler to
>> work, allowing good and safe code to work correctly.  For some bits of
>> code, extension keywords or other implementation-specific features must
>> be used for optimal efficiency due to limitations of the target
>> architecture.
>>
>> or
>>
>> 2. Change important keywords of C to work in a substantially different
>> way, breaking correct code, because you think it would be easier for
>> beginners to understand this non-standard variant of C.
>
> No implementation will be able to run all correct code.  Each of the above
> approaches will run some programs that the other would not.  If some
> particular program will run usefully on the second approach, but would be
> unable to run using the first because of resource constraints, then I would
> suggest the second implementation would be more useful for the purpose of
> running that program.
>

Clearly it is possible to write code that can be run by the second 
implementation.  And clearly it is possible to write code that /cannot/ 
be run by the first implementation (just use enough constant data to 
overflow the ram in the microcontroller, avoiding the 
implementation-specific features needed to keep that constant data in 
flash).  But with the second implementation, standard programs that 
should be able to run (i.e., they are within the limitations of ram, 
rom, etc.), will compile cleanly but have different and incorrect 
behaviour compared to other conforming C implementations.  And that is 
the vital difference.

>> I agree that for some targets, it can be better to make some breakage to
>> C standards conformity.  But that should be kept to the absolute
>> minimum, be well documented, not break working code silently, and
>> preferably require a command-line switch to activate it or give warnings
>> about the odd behaviour.  It is reasonable, for example, for a C
>> implementation on a small 8-bit micro to make "double" limited to 32
>> bits.  It is /not/ reasonable to change the meaning of a keyword like
>> "const" to mean something subtly different, or to skip the zeroing of
>> uninitialised data.
>
> Most code won't need to convert a const-qualified pointer to a non-qualified
> pointer and then later convert back to a const-qualified pointer and access
> ROM/flash.  While a compiler should have an option to make all pointers use
> the same format as const-qualified pointers, I would not expect that most
> programs would work better with that option disabled.

The problem is non-const data accessed through const qualified pointers. 
  And such code /is/ common:

extern int average(int noOfSamples, const int * pSamples);
int x[10];
int av = average(10, x);


>
>>> That's certainly a reasonable approach, and a quality compiler should
>>> probably offer it as an option, but for many purposes a non-conforming
>>> option that works like other compilers do may be more useful.
>>
>> No, the "break the const keyword" approach is not more useful.
>
> If a program would work correctly with or with or without that option, but
> be faster without, or if it would work correctly with that option but would
> not fit in the available code space without it, in what way would that
> option make the implementation more useful for purposes of running that
> program?

A C compiler should not be made to work only with a particular 
specialised variety of programs!  A compiler sold as a "C Compiler" 
should be made to correctly compile as many valid C programs as possible 
given the limitations of the target.

>
> If the Atmel can't use a const-qualified pointer to access RAM (I've not
> used that platform, so I don't know if it can), that would be a horribly
> broken abuse of the "const" keyword,

Exactly.  (The limitation is for one particular compiler for the AVR, 
not for all compilers for it.)

> but if "const"-qualified pointers
> can access either while unqualified pointers are limited to RAM, most
> programs would not be affected by an inability to round-trip const pointers
> through unqualified pointers nor directly use unqualified pointers to
> access ROM.
>

Certainly that would be much less of a problem.

[toc] | [prev] | [next] | [standalone]


Page 1 of 3  [1] 2 3  Next page →

Back to top | Article view | comp.lang.c


csiph-web