EmbeddedRelated.com
Forums
Memfault Beyond the Launch

Fundamental C question about "if" statements

Started by Oltimer September 20, 2015
On Sun, 20 Sep 2015 22:43:28 +0200, David Brown wrote:

> On 20/09/15 21:17, Tim Wescott wrote: >> On Sun, 20 Sep 2015 19:22:12 +0800, Oltimer wrote: >> >>> Learning some embedded C using Microchip's C18 Lite. >>> >>> >>> I tried the following and it misbehaved: >>> >>> if( 10 < my_variable < 20 ) >>> { //do this......} >>> >>> >>> >>> Using the following works: >>> >>> if (( x > 10) & ( x < 20)) >>> { //do this.....} >>> >>> >>> >>> Is it normal that C cannot handle complexities such as "a < b < c" ? >>> I would have thought a compiler could easily work out what was >>> intended. >>> >>> Thanks. >> >> It's not a matter of it being a "complexity", it's a matter of you >> having an incorrect preconception of how the C compiler works. >> >> C does not define "a < b < c" as a valid combination. It DOES define >> "a < b" as something that it can interpret and cough up an integer >> result. Then it can test that integer result against c and do something >> perfectly sensible. >> >> So when if write >> >> if (10 < my_variable < 20) >> { etc. } >> >> the C compiler -- per its specification -- interprets it as >> >> if ((10 < my_variable) < 20) >> { etc. } >> >> (or it interprets it as if (10 < (my_variable < 20)) -- I'm not sure >> which, if either, is insisted upon). > > The < operator associates to the left, so it is ((10 < var) < 20). But > it's good that you don't know - code should always make these things > explicitly clear with parentheses or by using intermediate variables, > rather than relying on the C operator precedence and association rules. > > >> Of course, when C evaluates a boolean expression and coughs up an >> integer, the only thing that's guaranteed in the C standard is that a >> false expression should evaluate to zero, and a true one evaluate to a >> non-zero value -- I've seen 1 and -1, and there's nothing to say it >> can't be 42, or 69, or anything else the compiler writer wants. > > No, the result of a relational operator is always 0 or 1. The compiler > will treat any non-zero value as "true" for things like an if statement, > but it always returns 1 for true for relational or logical operators. > > (Actually, in a case like this the compiler can see that the result is > always less than 20, and therefore the if statement is always true and > be eliminated entirely.) > > >> This is spelled out very well in "C, A Reference Manual" by Harbison >> and Steele. There are other books out there (K & R comes to mind), and >> Harbison and Steele may not even be the best -- but I find that >> Harbison and Steele is a very good book for my purposes. > > I am looking at the C standards here (C11, document N1570). > > >> And in case you missed it -- pay attention to the difference between >> '&' and '&&'. >> >> if (0x0001 & 0x0100) >> { etc. } >> >> will have a different result from the expression >> >> if (0x0001 && 0x0100) >> >> The original authors of C played fast and loose with the notion of a >> boolean value and no one ever "fixed" it (because it would have meant >> breaking a lot of code). So it's up to you to pay attention to when an >> expression involves integers disguised as booleans, and when it >> involves real honest to gosh integers. This means that while >> >> if (some_integer_value != 0) >> { do something } >> >> has the same effect as >> >> if (some_integer_value) >> { do something } >> >> you should still use the former. > > That's arguably true, but it's a matter of style - you won't get 100% > agreement here. Many people would feel that the "if" statement in C > tests that something is not zero (or not null, for a pointer), and thus > the "!= 0" is redundant. > >> Similarly, it's probably better to use >> >> if (some_boolean_value != FALSE) >> >> (even though this means you have to define TRUE) as opposed to >> >> if (some_boolean_value) >> >> but it would be much much worse to use >> >> if (some_boolean_value != 0) >> >> because then it looks like an integer. > > I would suggest that you stick to using the Boolean type bool (from > <stdbool.h>) when you mean Boolean - don't use integers there at all. > This also means using C99 (or C11) instead of ANSI/C89/C90, but that's > good advice anyway unless you are forced to program in a seriously > outdated version of the language. > > Once you are using "bool" for your booleans, it is perfectly safe and > sensible to write "if (some_boolean_value)". > > >> (and note: if you do go defining TRUE and FALSE, I define them as >> >> #define TRUE (1 == 1) >> #define FALSE (1 != 1) > > Far better is to include <stdbool.h> and use bool, true and false. > > And if you really have to use a pre-C99 compiler, and that compiler > doesn't come with a <stdbool.h> as an extension (some do), then use: > > typedef unsigned char bool; > #define true 1 #define false 0 > > You will get almost the same effect as a proper "_Bool" type, except > that casting an int to a bool will no longer work correctly, and you > need to use the !! operator explicitly: > > int i; > _Bool b = i; > > is equivalent to: > > int i; > unsigned char b = !!i; > > The !! operator keeps 0 as 0, and turns anything non-zero into 1. > > >> because that way you're not trying to guess what the compiler uses for >> true. We have Real Live Compiler Writers lurking on this list, so if >> David Brown says he has a better way -- pay attention!) >> >> > I don't write compilers - though I heard once that there was a different > David Brown that is involved in compiler development somewhere. But I > read the C standards more than most people, I have helped out real > compiler writers a little, haunt comp.lang.c, and have worked with a > good many different compilers over the years. I think that's enough to > justify paying attention to me - but not enough to consider my advice as > infallible! > > And another thing to be careful of here, is that compiler writers are > not infallible either - sometimes they misinterpret the standards, and > fail to follow the rules. > > And we do have a /real/ Real Live Compiler Writer who lurks on this list > - Walter Banks. So if he makes a post here, we should all pay > attention.
Whoops #1: I mostly program in C++ these days, and missed the introduction of stdbool.h. Bad me. Whoops #2: 0 and 1 only? Good! This wasn't always the case, or at least about 30 years ago there was a compiler (Borland or Microsoft, can't remember which) that used 0 and -1 (or at least 0xffff). Whoops #3: I was thinking of Walter, and your name dribbled off my fingertips. Oh well. Thank you very much for _all_ the corrections -- I hate to be responsible for misleading people, so I appreciate it when people see my mistakes and nudge me back into line. -- www.wescottdesign.com
On Sun, 20 Sep 2015 21:26:15 +0000, John Temples wrote:

> On 2015-09-20, Oltimer <nup@nup.com> wrote: >> >> Learning some embedded C using Microchip's C18 Lite. > > That's probably not the best compiler for learning. It's obsolete, and > has many quirks to trip up a beginner, like generating warnings on valid > code. XC8 is the Microchip's currently supported compiler for all of > the 8-bit parts.
Any C compiler that targets an 8-bit PIC is not the best for learning -- at least not for learning C. The PIC architecture is a very bad fit to the C virtual machine, and as such a compiler writer is forced to choose between making a compiler that is not compliant to the standards, or making a compiler that generates hugely inefficient code. I can't speak to the XC8 compiler, but the C18 not only wasn't compatible, it pretty much required you to do Really Bad Things in order to get the most efficient code (this, by the way, is the same problem exhibited by the 8051 -- it's a totally different architecture from the PIC, but it misses the C virtual machine by a similar-sized mile). Given that you can get ARM Cortex-M0 parts that are nearly as small as the smallest 8-bit parts, and are nearly as cheap (I think they get down to $0.75 or less in onsies from DigiKey), I don't see any reason not to use a part that's a better fit to the language. -- www.wescottdesign.com
Tim Wescott <tim@seemywebsite.com> wrote:
> On Sun, 20 Sep 2015 22:43:28 +0200, David Brown wrote:
(snip on 10<x<20)
>> No, the result of a relational operator is always 0 or 1. The compiler >> will treat any non-zero value as "true" for things like an if statement, >> but it always returns 1 for true for relational or logical operators.
(big snip)
> Whoops #2: 0 and 1 only? Good! This wasn't always the case, or at least > about 30 years ago there was a compiler (Borland or Microsoft, can't > remember which) that used 0 and -1 (or at least 0xffff).
People would notice that one pretty fast. But many Fortran compilers, I knew if back to DEC Fortran IV compilers, do that as an extension. Fortran has a LOGICAL type that normally doesn't convert to/from INTEGER (or other types). Some compilers allow it, and -1 isn't unusual. (They convert the bit pattern. It is then not so obvious which value is .TRUE. and which .FALSE. in an IF statement.) PL/I uses bit strings, '0'B and '1'B as boolean values, and convert to numeric 0 or 1 when needed. Personally, I like 0 and 1 better than 0 and -1. Note that C allows for ones complement where all bits 1 is negative zero. Do be careful with that one. -- glen
On 2015-09-20, Tim Wescott <tim@seemywebsite.com> wrote:
> On Sun, 20 Sep 2015 21:26:15 +0000, John Temples wrote: > >> On 2015-09-20, Oltimer <nup@nup.com> wrote: >>> >>> Learning some embedded C using Microchip's C18 Lite. >> >> That's probably not the best compiler for learning. It's obsolete, and >> has many quirks to trip up a beginner, like generating warnings on valid >> code. XC8 is the Microchip's currently supported compiler for all of >> the 8-bit parts. > > Any C compiler that targets an 8-bit PIC is not the best for learning -- > at least not for learning C. The PIC architecture is a very bad fit to > the C virtual machine, and as such a compiler writer is forced to choose > between making a compiler that is not compliant to the standards, or > making a compiler that generates hugely inefficient code.
And C18 managed to accomplish both of those at the same time. XC8 is much more standards-compliant.
On Sun, 20 Sep 2015 18:29:00 -0500, Tim Wescott <tim@seemywebsite.com>
wrote:

>Whoops #2: 0 and 1 only? Good! This wasn't always the case, or at least >about 30 years ago there was a compiler (Borland or Microsoft, can't >remember which) that used 0 and -1 (or at least 0xffff).
Microsoft's OLE (1990) defined type VT_BOOL as a 16-bit value with VARIANT_TRUE = -1 (0xFFFF) and VARIANT_FALSE = 0. The original Visual Basic was designed concurrently with OLE and enshrined that usage. From OLE it carried over into COM and eventually into dotNET. It's no longer defined as 16-bit, but under the hood the value of "true" still is -1. Every compiler that supports OLE or COM/ActiveX, and every dotNET language compiler has to deal with this. In "safe" code, C# allows _converting_ [not casting] boolean to integer: the conversion is the moral equivalent of (x == true) ? 1 : 0 so that the result is what C(++) programmers expect. From unsafe code you can see what the actual value is. George
On Sun, 20 Sep 2015 14:03:17 -0500, Tim Wescott <tim@seemywebsite.com>
wrote:

>On Sun, 20 Sep 2015 13:37:18 +0200, David Brown wrote: > >> On 20/09/15 13:22, Oltimer wrote: >>> >>> Learning some embedded C using Microchip's C18 Lite. >>> >>> >>> I tried the following and it misbehaved: >>> >>> if( 10 < my_variable < 20 ) >>> { //do this......} >>> >>> >>> >>> Using the following works: >>> >>> if (( x > 10) & ( x < 20)) >>> { //do this.....} >>> >>> >>> >>> Is it normal that C cannot handle complexities such as "a < b < c" ? >>> I would have thought a compiler could easily work out what was >>> intended. >>> >>> Thanks. >> >> No, compilers cannot work out something like "a < b < c" - it is >> interpreted as though it were "(a < b) < c", where "(a < b)" is either 0 >> or 1. > >Well, you _could_ write a compiler to understand an "a < b < c" >construct, probably fairly easily and directly. It just wouldn't match >with most compilers.
Cobol has done something like that for years with "implied subjects". While for simple cases it's fairly clear: IF X > 10 AND < 20 THEN... But weird and difficult to understand cases quickly arise: IF A > B AND NOT < C AND D THEN... In case you're wondering, the last term is "A < D", *not* "A NOT < D". OTOH, if the NOT is on the implied operator, it *does* propagate: IF A NOT > B AND C THEN... Where the second term is "A NOT > C". Interestingly(?) the rules actually changed fairly significantly, I think in Cobol-68 - the older rules were even weirder. In general the rule is to avoid implied subjects like the plague. One thing that Cobol did largely prove, is that "natural language" makes for a lousy programming language.
On 20/09/15 23:59, Tim Wescott wrote:
> On Sun, 20 Sep 2015 21:11:50 +0000, Aleksandar Kuktin wrote: > >> On Sun, 20 Sep 2015 21:24:03 +0200, Wouter van Ooijen wrote: >> >>>> Well, you _could_ write a compiler to understand an "a < b < c" >>>> construct, probably fairly easily and directly. It just wouldn't >>>> match with most compilers. >>> >>> I'd say that you could design a *language* that has the >>> natural-language interpretation of a < b < c, and then someone could >>> implement that language in a compiler. >>> >>> Writing a C compiler that implements that natural-language >>> interpretation would be a nice april fools day project. But than can be >>> done simpler by for instenace sneaking in a >>> >>> #define while if >>> >>> Wouter >> >> This #define is a pretty good way of making the language explode. Even >> if you #undef it immediately after using it. >> >> "a < b < c" isn't strictly needed, and its implementation would probably >> wreck havoc with the operator evaluation. "(a < b) && (b < c)" is good >> enough. > > I don't think anyone said it was needed, or even a good idea -- I, at > least, was only arguing with the assertion that a compiler couldn't be > written that couldn't compile the expression "a < b < c" as it would be > understood by a mathematician.
Yes, when I wrote that the compiler couldn't understand such an expression, it is just that a C compiler can't understand it. As noted by other people, Python certainly /can/ understand it. But I don't think it is a good idea for languages to support such things, except perhaps if they are specifically aimed as mathematical tools. People can get carried away in believing the code they write is "normal" maths if the language is too smart at its interpretation - it is clearer, and therefore easier and safer, if there are simple and absolute rules involved.
> > The whole issue would be a non-issue if C had been a strongly typed > language -- boolean values have no magnitude, so any expression that > compares a true/false value against any numerical value should, properly, > cause a compilation error. But C doesn't do that, and couldn't be made > to do it at this late date without generating chaos in the existing code > base. So it's up to the developer to recognize that there's a quirk in > the language and deal with it. >
Agreed - in a strongly typed language, this would be an error. (Though note that C++ is a strongly typed language with a proper boolean, but due to backwards compatibility with C, the result of the "<" operator is an int, not a boolean, and you still have the same problem.) The best you can get with C is to use a compiler (or lint tool) that gives warnings as though the language had stronger typing, and spots cases where the compiler may not be interpreting the code in the way the programmer expects. (In this case, "gcc -Wall" will spot these issues.)
On 21/09/15 00:01, Tim Wescott wrote:
> On Sun, 20 Sep 2015 20:42:10 +0200, David Brown wrote: > >> On 20/09/15 19:40, Les Cargill wrote: >>> Oltimer wrote: >>>> >>>> Learning some embedded C using Microchip's C18 Lite. >> >> I hadn't noticed that the OP was using a PIC18. Of course, if you are >> interested in learning about C, programming embedded systems, or simply >> using a half-decent compiler, then pick any microcontroller except a >> Microchip PIC. PICs (and 8051's) should be banned from any beginners on >> the grounds that they cause more harm than good. > > I agree with your two outer statements, but a PIC isn't a bad processor > if you stick to assembly (and projects small enough that assembly is > reasonable). >
The PIC chips have some good points - they are extraordinarily robust. I have worked with a customer's card that had a 85C qualified PIC, and they were looking for ways to push it beyond the 160C or so where it currently stopped working. And once Microchip starts delivering a PIC device, they never seem to obsolete them - you can still buy devices that are 15 years old or more. But they are a horrible CPU to work with - in C or in assembly. At least with assembly you know you are working with something device-specific, so it is less of a surprise.
On 21/09/15 00:01, Tim Wescott wrote:
> On Sun, 20 Sep 2015 20:42:10 +0200, David Brown wrote: > >> On 20/09/15 19:40, Les Cargill wrote: >>> Oltimer wrote: >>>> >>>> Learning some embedded C using Microchip's C18 Lite. >> >> I hadn't noticed that the OP was using a PIC18. Of course, if you are >> interested in learning about C, programming embedded systems, or simply >> using a half-decent compiler, then pick any microcontroller except a >> Microchip PIC. PICs (and 8051's) should be banned from any beginners on >> the grounds that they cause more harm than good. > > I agree with your two outer statements, but a PIC isn't a bad processor > if you stick to assembly (and projects small enough that assembly is > reasonable). >
The PIC chips have some good points - they are extraordinarily robust. I have worked with a customer's card that had a 85C qualified PIC, and they were looking for ways to push it beyond the 160C or so where it currently stopped working. And once Microchip starts delivering a PIC device, they never seem to obsolete them - you can still buy devices that are 15 years old or more. But they are a horrible CPU to work with - in C or in assembly. At least with assembly you know you are working with something device-specific, so it is less of a surprise.
On 21/09/15 00:01, Tim Wescott wrote:
> On Sun, 20 Sep 2015 20:42:10 +0200, David Brown wrote: > >> On 20/09/15 19:40, Les Cargill wrote: >>> Oltimer wrote: >>>> >>>> Learning some embedded C using Microchip's C18 Lite. >> >> I hadn't noticed that the OP was using a PIC18. Of course, if you are >> interested in learning about C, programming embedded systems, or simply >> using a half-decent compiler, then pick any microcontroller except a >> Microchip PIC. PICs (and 8051's) should be banned from any beginners on >> the grounds that they cause more harm than good. > > I agree with your two outer statements, but a PIC isn't a bad processor > if you stick to assembly (and projects small enough that assembly is > reasonable). >
The PIC chips have some good points - they are extraordinarily robust. I have worked with a customer's card that had a 85C qualified PIC, and they were looking for ways to push it beyond the 160C or so where it currently stopped working. And once Microchip starts delivering a PIC device, they never seem to obsolete them - you can still buy devices that are 15 years old or more. But they are a horrible CPU to work with - in C or in assembly. At least with assembly you know you are working with something device-specific, so it is less of a surprise.

Memfault Beyond the Launch