Close
0%
0%

PotentiallyUseful/Frustrating/Obscure C/GCC/Make

Notes about unexpected findings in C/GCC... (and Make, and plausibly Bash, etc.)

Similar projects worth following
Exactly what it says... Things I've discovered that I didn't expect...
Could be useful, could be frustrating, could be unexpected, who knows...

Table Of Contents:

  1. #defined function-names after function-definitions (confusing)
  2. DO NOT TRUST: that there will be warnings/errors for uninitialized variables!!!
  3. (gnu make) Makefile Recursive inclusion follies (6-17-15)
  4. (gnu make) makefile auto-creation follies (6-17-15)
  5. (xc32-gcc, plausibly others?) escaping command-line quotes, etc. (7-14-15)
  6. function-like macros with/without return-values (7-16-15)
  7. TODOs and Qs (Latest: 1-29-16)
  8. Code-Size reduction ideas... check out #limited-code hacks/tips (added 11-26-16)
  9. I HAVE NOT been keeping this ToC up to date... check out the logs!

OTHER REFERENCES:

  1. computed-gotos, more efficient state-machines than using switch() ( @Analog Two's #reprint: modern printf )

1. #defined function-names after function-definitions

Thanks to @LazyHD for the reminder: This is generally considered BAD PRACTICE. But there may be times when you'll come across it's having-been-done (and most-likely by mistake!) and then it will be quite confusing.

(See his comment, below. But, briefly, #define's should be CAPITALIZED to avoid such confusion.)

#include <stdio.h>

//Uncomment one of the '#define function' lines to see what I'm sayin'

//Uncommenting this one results in an error...
//#define function() printf("running '#define function()'\n")

//And of course, disabling both #defines, results in this being called.,,
void function(void)
{
   printf("running actual 'void function(void)'\n");
}

//Uncommenting this one results in no errors/warnings
// and *this* is called, not the actual function.
#define function() printf("running '#define function()'\n")

int main(void)
{
   function();
}

This can be royally confusing... and SHOULD NOT BE DONE INTENTIONALLY.

Say you've a function between 'void function(void)' and the second '#define function()' which happens to call function()... it'll call the actual function, but main will call the #define... right? I dunno, it's risky-business. DON'T DO THIS. More-complicated programs, however, might just cause something like this, most-likely by mistake, e.g. through #if statements...


2. DO NOT TRUST: that there will be warnings/errors for uninitialized variables!!!

GCC: This one has bit me in the butt so many times...

int function(int b)
{
   int a;
   if(b==3)
      a=3;
   return a;
}
CLEARLY: a will only be assigned a value IF b==3!

CLEARLY: "-Wuninitialized" and "-Werror=uninitialized" is designed *exactly* for the purpose of noting when a variable like int a; could be used without being assigned a value! GCC WILL NOT catch this case... at least with the versions I've used. You WILL NOT receive a warning, nor error, in this case! And, allegedly, there's no intention to fix this glitch.

There's a good discussion on it HERE.


3. (gnu make) Makefile Recursive Inclusion Follies

(initially poorly-titled: 'include $(INCLUDABLES)' wherein those 'INCLUDABLES' modify the variable 'INCLUDABLES')

Say you have a bunch of makefile 'snippets' you'd like included in a makefile.

makefile:

INCLUDABLES += inc1.mk
INCLUDABLES += inc2.mk

include $(INCLUDABLES)

http://inc1.mk:

INCLUDABLES += inc1a.mk

The end-result appears clear, at least on my system...

My search-fu is failing me. is this result expected/able to go cross-platform? cross-makes?

The end-result, on my system, is that apparently the call to 'include $(INCLUDABLES)' works with the variable 'INCLUDABLES' AS IT EXISTED when the 'call was placed'

So, inc1a.mk is never included.

(This goes against my [utter-lack-of-]understanding of make's "multipass technique", but goes completely in favor of my having an utter-lack-of-understanding of it, in the first place ;)

Thoughts?


4. (gnu make) makefile auto-creation follies

It's possible to have 'make' auto-create files it wishes to include...

e.g.

makefile:

include includeMe.mk
...

if the file 'ncludeMe.mk' doesn't exist, 'make' can try to auto-create it based on a rule...

makefile continued:

# Rules:

#Default ('make')
all:
    @echo "'make [all]'"

#Missing makefile snippet:
%.mk:
    @echo "creating missing makefile snippet"
    @touch $@"

Cool.

Now... what...

Read more »

  • bash aliases WHOOPS

    Eric Hertz02/27/2017 at 16:48 0 comments

    I can't believe I've never run into this before...

    I've had this in my .bash_profile for *years*

    alias mmv='echo "" ;  echo "DON'\''T GET COCKY! mv -i set in .bash_profile, this is NOT default. " ;  echo "  Default is to overwrite WITHOUT PROMPTING" ;  echo "" ;  /bin/mv -i'

    Now I did:

    diff file1 file2 && mmv file1 otherDir

    -----------

    Now, apparently, 'alias' doesn't act like a script or a function, as I'd long-understood.

    Apparently it literally just pastes the contents on the line... so what I'm getting, instead is essentially:

    diff file1 file2 && echo "" ; ..... ; mv file1 otherDir

    I have a *really* hard time believing I never ran into this before. REALLY hard time. Literally *years* I've used this alias... and never *once* ran into this?! But, that's the claim.

    So, those ';'s need to be replaced with '&&'.

    -----------

    And upon looking at the alias with fresh eyes, I should've seen that it wasn't treated like a function... since it's not using arguments...

  • Interrupts, Volatiles, and Atomics... and maybe threads?

    Eric Hertz11/13/2016 at 16:54 0 comments

    When using global-variables shared between your main() function and an interrupt (or two threads?), there are a lot of potential disasters one might not expect...

    ----------

    I'm no expert, here... but I've learnt a few things along the way, which could be handy for others. There's probably an entire course in most Computer-Science degrees on the matter, but maybe this is a decent starting-point for those who haven't run into it...

    ----------

    First: Use "volatile" correctly...

    Here's a pretty great writeup on the matter, so I won't go into it: http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

    Read that, because some of the following relies on your having-done...

    ---------

    Now for the fun stuff... That page doesn't describe several other common pitfalls...

    E.G. say you have:

    while( volatile_int != 0 ) 
    { 
       do something with volatile_int 
    }

    Will it reread volatile_int *each* time it's referenced within that while-loop, or will that entire iteration of the while loop use that value of volatile_int? Or worse, maybe:

    if( volatile_int > 0 )
    { 
      do something with volatile_int 
    } 
    else if ( volatile_int < 0 )
    {
      do something else with volatile_int
    }
    else // volatile_int == 0
    {
      do something else, again, with volatile_int
    }
    

    In cases like these, it's too confusing (for me) to rely on whatever the standard might claim in some vague reference-manual somewhere hard to find... Nevermind what would happen when an interrupt occurs between testing the first "if" and the next "else if"...

    So best to use a separate *local* variable... e.g.

    int temp_int;
    while( (temp_int = volatile_int) !=0 )
    { 
      do something with temp_int 
    }

    or

    int temp_int = volatile_int;
    if( temp_int > 0 )
    { 
      do something with temp_int 
    } 
    else if ( temp_int < 0 )
    {
      do something else with temp_int
    }
    else // temp_int == 0
    {
      do something else, again, with temp_int
    }

    And, when needing to write back to volatile_int...?

    Best to write to a temp variable and *only once* write that temp-variable back to the global.

    Even still, there's some difficulty. If your interrupt *and* main both write to the same variable you can get into some trouble, depending on the implementation... This is where mutual-exclusion comes into play, and I thankfully haven't had to mess with that much.

    ---------

    (For most of my needs, an interrupt writes a variable, main reads it back, and *if* main needs to write it, it's usually just as an indicator that the value has been processed... more on that in a second).

    ---------

    And, finally, it may not be *as* common on a 32-bit device, but definitely a common problem with 8-bitters... Consider a global volatile int64_t being written in an interrupt and used in main():

    temp_int64 = volatile_int64;
    

    On a 32-bit system, that will most-likely require *two* instructions, each writing 32-bits of temp_int, one 32-bit word at a time. If the interrupt occurs *between* those writes, temp_int64 will contain a mess. Here's a 16-bit example on an 8-bit processor:

    volatile_int16 = 0x00ff;
    
    main: 
     temp_int16 = volatile_int16; 
     // -> temp_int16[low byte] = 0xff; 
     // <INTERRUPT>
    
    interrupt: 
     volatile_int16 = 0xff00; 
     // <return to main>
    
    main: 
     temp_int16 = volatile_int16; //(continued) 
     // -> temp_int16[high byte] = 0xff;
    
    //now, in main, temp_int16 == 0xffff
    
    

    Bad news!

    This one's a real problem, because most people don't see it just looking at the code. *And* it happens *very* rarely that the interrupt will occur at *exactly* that moment.

    But if you're not careful the consequences could be horrendous

    (sudden-acceleration of cars? pace-makers firing when not needed? etc. etc. etc.)

    So, then, it's somewhat common to change it in main:

    <disable interrupts>; 
    temp_int16 = volatile_int16; 
    <reenable interrupts>

    This makes the two-instruction assignment "atomic", it cannot be interrupted.

    Of course, this assumes you already had interrupts enabled and want them reenabled after your assignment to temp_int16... but what if you don't *know* whether interrupts are enabled but want...

    Read more »

  • Oh My... TRUE = NON-ZERO pitfalls

    Eric Hertz07/03/2016 at 20:24 6 comments

    Hah. somehow it'd never occurred to me... Hopefully because I never needed it like this... otherwise there may be bugs all over the place completely unknown.

    ---------------

    So, "TRUE" in the sense of

    if(something){<do something else>}
    ... is whenever something is non-zero. NON-ZERO.

    Right, that's easy... and quite handy.

    Checking whether a pin is high on a port...?

    if( PORTA_INPUT_REGISTER & (1<<pinNum) ) { ... }
    does the trick quite nicely...

    And, with optimization, that's nothing more than a <read-register> and an <and> (the left-shift is turned into a constant, assuming pinNum is a constant).

    Okie Dokie!

    So now, let's use that somewhere a bit more complex... right? Not sure *why* exactly, but say you want to for some reason...

    e.g.

    uint8_t portInputs = PORT_INPUT_REGISTER;
    
    if ( portInputs & (1<<pinNum) ) { ... }
    Awesome.

    Now say you're using that as an output to a function... why-not-eh?

    //Returns NON-ZERO when the pin is high
    //ZERO when it's low
    uint8_t IsPinHigh(uint8_t pinNum)
    {
       return ( PORT_INPUT_REGISTER & (1<<pinNum) ); 
    }
    ...
    if(IsPinHigh(3)) { ... }

    Woot, it's all good...

    So, doing-so this way saves quite a few instructions... Another way of doing something like this would be to have IsPinHigh() return ONE (the value, 0x01) if the pin is high, rather than NON-ZERO. But, doing-so requires an additional test, a non-zero test. which I guess would probably only be one or two instructions, but they're instructions, nonetheless. Another way, still, (and more obvious to me, for some reason) would be to >> the result back, e.g.:

    uint8_t IsPinHigh(uint8_t pinNum)
    {
       return ( ( PORT_INPUT_REGISTER & (1<<pinNum) ) >> pinNum ); 
    }
    So, now you've got a ton of instructions... but your output is either 0 or 1, rather'n 0 or (1<<pinNum).

    Anyways, we *know* it's going to be used *only* in cases where non-zero or zero is what matters... e.g. in if(IsPinHigh()) statements, so why waste all those extra instructions?

    Now, who knows, maybe you want it to be a little more complex, still... I dunno why... (back to the Zero v. Non-Zero example):

    Or, heck, don't even bother making it more complex... We've got a pretty good setup, here. It works great!

    Awesome. Now move that same bit of code to another processor, maybe a 32-bitter, where the Port-Registers are 16bits wide...

    //Returns NON-ZERO when the pin is high
    //ZERO when it's low
    uint8_t IsPinHigh(uint8_t pinNum)
    {
       return ( PORT_INPUT_REGISTER & (1<<pinNum) ); 
    }
    ...
    if(IsPinHigh(13)) { ... }

    Whoops.

    Duh.

    See it?

    I mean, all we care about is a boolean value, yahknow, it's either true or not true... That *easily* fits in a uint8_t. But... Yeah. Probably not worth all this explanation, really, because yahknow it's so blatantly obvious, here... Even more fun when you're porting code that's already been written and long-functional and it's not all spelled-out for you like this.

    And worse, still, when the return-value of IsPinHigh() is stored in *yet another* variable, which may well be uint8_t as well...

    Oy.

    So, here's a nastier example where it might be *slightly* less-obvious.

    //Returns NON-ZERO when the pin is high
    //ZERO when it's low
    uint8_t IsPinHigh(uint8_t pinNum)
    {
       return ( PORT_INPUT_REGISTER & (1<<pinNum) ); 
    }
    
    //If isTrue is NON-ZERO, then doSomething
    //If isTrue is ZERO, then don't doSomething
    void doSomethingWhenTrue(uint8_t isTrue)
    {
        if(isTrue)    { ... }
    }
    ...
    
    int pinIsHigh = IsPinHigh(3);
    doSomethingWhenTrue(pinIsHigh);
    Or, imagine you saw the glaring fact of IsPinHigh()'s return-value, and fixed it *right away*, but instead of pinIsHigh having been an int, instead it was a uint8_t... Or, yahknow, there might be any number of combinations wherein it might even get missed by -Wconversion (see below).

    FYI: -Wconversion is a handy argument to throw at gcc... but you'll also get to see just how many conversions happen behind the scenes that you don't usually think about... E.G. did you know that ~(0xff) becomes an *int* (rather than the uint8_t it started as)? So, typedef or #define a new type for a case...

    Read more »

View all 3 project logs

Enjoy this project?

Share

Discussions

[deleted]

[this comment has been deleted]

Eric Hertz wrote 07/14/2015 at 14:17 point

Indeed, I was taught the same practice... Thanks for the reminder, I'll put a note of this in there!

  Are you sure? yes | no

Arduino Aficionado wrote 06/27/2015 at 11:29 point

Wow this is great.

  Are you sure? yes | no

Eric Hertz wrote 07/14/2015 at 14:16 point

Awesome, thanks! Not sure how I missed this message.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates