EmbeddedRelated.com
Forums

Fundamental C question about "if" statements

Started by Oltimer September 20, 2015
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. -- www.wescottdesign.com
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). 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. 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. 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. 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. (and note: if you do go defining TRUE and FALSE, I define them as #define TRUE (1 == 1) #define FALSE (1 != 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!) -- www.wescottdesign.com
> 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
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.
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.
Oltimer <nup@nup.com> writes:
> Is it normal that C cannot handle complexities such as "a < b < c" ?
No, C does not do that, several people have explained and advised about enabling warnings. At a meta level, I'd like to say that C is a very unforgiving language with a lot of hazards and pitfalls. It's fine to get started by experimenting but before doing anything serious with it, you should really read some reference documentation to make sure you understand the details. This is worth reading: http://blog.regehr.org/archives/213
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.
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. > > Get a chip with a Cortex M core, or at least an AVR or msp430. Then you > can use proper development tools and program in normal C. > >> >> This book: >> >> http://www.amazon.com/The-Programming-Language-Brian-Kernighan/dp/0131103628 >> >> >> will save you hours of grief. > > It will also cause you hours of grief - because although it is a fairly > complete guide to C, it is not the best tutorial out there, it is for a > seriously outdated version of C (C11 is the current standard, though > there are few practical changes from C99. But C99 is a significantly > better programming language than ANSI C for most uses), and it > concentrates on C for big systems - not for embedded systems. > > I don't know what the best choice of tutorial is for learning modern > embedded C, but the oldest known C book is not it. > > >
It still works. It's nearly universal. Learning every detailed corner of the language will take even more time. If you constrain yourself to mainly K&R plus a couple simple extensions you will produce more readable code than in any other way. Firs learn K&R, then learn the heresies that came later. You will then know why the heresies are good things. -- Les Cargill
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. 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. -- www.wescottdesign.com
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). -- www.wescottdesign.com