Fundamental C question about "if" statements

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.

Reply to
Oltimer
Loading thread data ...

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. A compiler is merely a computer program that follows set rules - it is not human, and cannot apply common sense to interpret what you write.

Your second version is also wrong. In C, the "&" operator is a bitwise and - what you want is a logical and which is "&&". In a number of circumstances, such as this one, they have the same logical effect - but don't make that mistake. Learn the difference, and use the correct version. (The same applies to bitwise or | and logical or || ).

So the correct version is :

if ((x > 10) && (x < 20)) { // Do this... }

You'll get the hang of it - just don't assume C is normal mathematics, and be careful to be accurate about what you write.

Reply to
David Brown

Thanks David. The reassurance I'm on the right track is good.

Yes, looking at it I can see that it should be "&&"

Thanks again!

Reply to
Oltimer

There are some computer languages that do allow a < b < c (like python).

The key point here is that C does not define the ability to do this as a special case of the syntax.

Reply to
Richard Damon

Confusing & with && is a common mistake as is mixing = with ==. Both can be hard to debug too.

--

Rick
Reply to
rickman

Make heavy use of whatever warnings your compiler can give you - gcc with lots of warnings can pick up on many such cases.

Reply to
David Brown

Yeah, another common mistake is thinking warnings are just "warnings". Code so that there are none.

--

Rick
Reply to
rickman

Agreed - warnings are an indication that you've got something wrong, or at least risky, in your code (or an indication that you've got a poor quality compiler that has silly ideas about warnings!). The more you can find out at compile-time, rather than run-time (or after delivery time), the better.

In this case, "-Wall" in gcc includes "-Wparentheses" which warns about "a < b < c", and also something like "if (x = y) { ... }". "-Wall" also includes "-Wunused-value", which is commonly triggered if you write something like "x == y;" instead of "x = y;".

Of course, the OP may be using a compiler other than gcc, though gcc is increasingly common - other compilers will typically also have warnings for that sort of basic mistake.

Reply to
David Brown

This book:

formatting link

will save you hours of grief.

"
Reply to
Les Cargill

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.

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.

Reply to
David Brown

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
Reply to
Tim Wescott

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
Reply to
Tim Wescott

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

Reply to
Wouter van Ooijen

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.

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.)

I am looking at the C standards here (C11, document N1570).

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.

I would suggest that you stick to using the Boolean type bool (from ) 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)".

Far better is to include 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 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.

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.

Reply to
David Brown

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:

formatting link

Reply to
Paul Rubin

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.

Reply to
Aleksandar Kuktin

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.

Reply to
John Temples

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
Reply to
Les Cargill

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
Reply to
Tim Wescott

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
Reply to
Tim Wescott

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.