Author Topic: GCC global variables  (Read 3735 times)

0 Members and 1 Guest are viewing this topic.

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 5579
  • Country: fr
Re: GCC global variables
« Reply #75 on: August 05, 2019, 10:39:15 pm »
Yep. I actually do parameters checking in almost ALL my functions actually, not just at the interface level .... That's fine. I'm no evangelist. ;D

Watch out, some youngster will re-invent this idea and call it "the explicit contract verification paradigm" and it will take off.

I know.

Design by contract is a thing.
Some newer languages include specific constructs for that. Reasonably recent versions of Ada, Eiffel (does anyone know this language) and quite a few others...

The main difference with doing it "by hand" (such as with C), aside from moderate syntax sugar coating, would be the underlying exception handling stuff. Many people don't like error checking in C because it's of course a lot more work than just letting the language handle exceptions by itself. That's certainly an area where C is very rough, but I happen not to mind that much the extra work, because in many cases, proper error checking accounts for maybe 50% (probably exxagerating it a bit here) of the total code, and that's fine with me. It's every bit as important as the actual processing. Boeing should probably take a hint. :P Anyway, of course YMMV.

 

Online hamster_nz

  • Super Contributor
  • ***
  • Posts: 2349
  • Country: nz
Re: GCC global variables
« Reply #76 on: August 05, 2019, 11:25:50 pm »
Many people don't like error checking in C because it's of course a lot more work than just letting the language handle exceptions by itself.

Many people hate it because it is so inconsistent - checking pointers are almost OK as NULL usually means "bad stuff happened". But for everything else all gloves are off, and you have to read the manpage for every function.

Is a return of zero success or failure? because in C -1 is 'true" and zero is 'false'... and then there failures that give an EAGAIN and EINTR in errno, where you need to retry....
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 5579
  • Country: fr
Re: GCC global variables
« Reply #77 on: August 05, 2019, 11:46:05 pm »
Many people hate it because it is so inconsistent - checking pointers are almost OK as NULL usually means "bad stuff happened". But for everything else all gloves are off, and you have to read the manpage for every function.

I still think it's mainly because it's a lot of work and housekeeping. I've seen a lot of code from people used to exception handling in other languages. Even with the added consistency and helpers, they tend NOT to handle all exceptions properly and tend to be pretty lazy about it.

Inconsistency - well, yes. Again, C is pretty rough in that respect. I try to keep a consistent convention within my own code, but for external libraries, I do with what I'm given. I agree with the annoying inconsistency (especially the fact that in some libraries return codes are errors, and in others, that's just a "fail" or "OK" and you have to get the exact error calling something else, or using some kind of global like errno. Yuck!), but I do not agree with the fact having to read the manual is a problem in itself. I've seen too many people using functions they *thought* they knew, and it turned out they didn't really. If it's not my own code, I certainly read the f* manual (and hope it's not screwed up). Socket functions are an example. I always re-read the manual pages. They are a bit of a mess.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 528
Re: GCC global variables
« Reply #78 on: August 05, 2019, 11:54:35 pm »
I would suggest if you currently have no plan to organize code, just pick something simple and work your way to something better.

Having a pair of h/c files for each peripheral and purpose seems to work well. Peripherals are already naturally organized in the datasheet and headers, so it also works well to use that as a separation point.

The h file is simply to inform anyone else what they can use from the corresponding c file, and provide any info to make use of them. If no one needs anything in the c file, no need for an h file. The compiler compiles one c file at a time, so treat every c file from the compiler perspective as if its the only file you know about. That's where headers come into play- you tell the compiler there is 'something' 'somewhere else' (via an include), and the compiler will believe you (the linker eventually will need more than just the paperwork, though).

From the original code Simon posted, a simple example would be to split out the tca0 things-

Code: [Select]
//=====================================
//tca0.h
//=====================================
#pragma once
#include <stdint.h> //we need it here (uint16_t), so use it here
uint16_t tca0_time();
void tca0_setup();

//=====================================
//tca0.c
//=====================================
#include "tca0.h" //only need in this case to make sure our version matches what we are telling others
#include <avr/io.h> //<xc.h>  //no one else needs this, so it goes here, not in tca0.h header
#include <stdint.h> //we need it here, so use it here (that's why there are include guards)
#include <avr/interrupt.h> //no one else needs this, so it goes here, not in tca0.h header
#include <util/atomic.h> //no one else needs this, so it goes here, not in tca0.h header

static volatile uint16_t time; //private, we tell no one

ISR(TCA0_OVF_vect){
    time++;
    TCA0.SINGLE.INTFLAGS = 0x01;
}

uint16_t tca0_time(){
    uint16_t ret;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ ret = time; }
    return ret;
}

void tca0_setup(){
    //setup tca0 (from setup.c code)
}
Now any other c file that needs the tca0 time, or needs to run tca0 setup, will just include tca0.h and they will have all the info they need. No one else needs to worry about potential atomic problems. If any changes needed to tca0, you already know which files to change- the c file with that name and the h file also if needed. With the 'public' tca0 vars/functions prefaced with tca0_, you will be informed where they are sourced every time you use them.

There are plenty of holes in the above code/explanations, but its a start.


Code: [Select]
Yep. I actually do parameters checking
One portion of 'parameter checking' can be done with enums-
Code: [Select]
//=====================================
//adc.h
//=====================================
#pragma once //actually, need old style inlcude guards with xc8-pic
#include <stdint.h> //needed here (uint16_t), so use here

typedef enum {
    adc_AVSS = 0x3B,
    adc_TEMP,
    adc_DAC1,
    adc_FVR1,
    adc_FVR2
} adc_ch_t;

uint16_t adc_read(adc_ch_t);


//=====================================
//adc.c
//=====================================
#include "adc.h"
#include <stdint.h>
#include <xc.h>

uint16_t adc_read(adc_ch_t ch){
    ADCON0bits.CHS = (uint8_t)ch;
    //other stuff
    return ADRES;
}
There is no way you can pass a channel that is incorrect, so no need to check if the channel is valid as the compiler already checked. Yes, you can cast to get around that, but by that point you know what you are doing and why and want no checking anyway.

My 2 cents.
« Last Edit: August 05, 2019, 11:59:13 pm by cv007 »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 5579
  • Country: fr
Re: GCC global variables
« Reply #79 on: August 06, 2019, 12:57:28 am »
There is no way you can pass a channel that is incorrect, so no need to check if the channel is valid as the compiler already checked. Yes, you can cast to get around that, but by that point you know what you are doing and why and want no checking anyway.

Well obviously parameters checking goes well beyond the parameters that can have a limited set of values, so that would only deal with this specific case.
Yet using enums (and using the corresponding enum types for the parameters/variables) for this kind of values is good practice IMO. Makes things a lot more readable.

But unfortunately, you're sadly mistaken if you believe the compiler will catch anything much.

Below a very simple example:

Code: [Select]
#include <stdint.h>

typedef enum {
    adc_AVSS = 0x3B,
    adc_TEMP,
    adc_DAC1,
    adc_FVR1,
    adc_FVR2
} adc_ch_t;

uint16_t adc_read(adc_ch_t ch)
{
return ch;
}

uint16_t Test1(void)
{
uint16_t n;
char yetAnother = 20;

n = adc_read(adc_TEMP); // Enum value

n = adc_read(10); // Some int value not in adc_ch_t

n = adc_read(yetAnother); // Some int value not in adc_ch_t from an intermediate variable of type char (why not?)

yetAnother++; // Let's spice things up
n = adc_read(yetAnother); // Still no luck...

return n;
}

Compiled with:
Code: [Select]
gcc -Wall -pedantic -c
Would you expect the second, third and fourth adc_read() call to yield a compiler warning because the value is not one of the enum values? Well, GCC doesn't give any warning, and -Wall -pedantic is pretty severe already. It kindly ignores the whole mishap... and no need to cast anything either...

Contrary to languages such as ADA and VHDL (and maybe others), C doesn't check whether values assigned or compared to enum variables are part of the listed values. It's pretty dumb in that respect.
« Last Edit: August 06, 2019, 01:05:05 am by SiliconWizard »
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 528
Re: GCC global variables
« Reply #80 on: August 06, 2019, 01:19:12 am »
>There are plenty of holes in the above code/explanations, but its a start.

I should have added there were holes in the code below also.
You are right. The only help is the user forcing themselves to lookup/use the enum values.

I mostly use c++, where this type of thing actually works as intended. There are quite a few nice things in c++, and assuming c acts the same way in some of these areas can be a problem when using both.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 5579
  • Country: fr
Re: GCC global variables
« Reply #81 on: August 07, 2019, 03:36:58 pm »
You are right. The only help is the user forcing themselves to lookup/use the enum values.

I mostly use c++, where this type of thing actually works as intended. There are quite a few nice things in c++, and assuming c acts the same way in some of these areas can be a problem when using both.

Oh, makes sense then. Yes C++ makes much fewer implicit casts than C, so an enum type and int are not compatible unless you explicitely cast.
And you're right, mixing C and C++ rules is a very bad idea - it leads to all kinds of potential bugs, some much more serious than not statically checking some implicit cast... I've seen that quite often from developers mainly writing C++ and that had to write C for some embedded projects...

All that said, what you suggest is good (in C++), but it doesn't replace explicit tests. All errors that could be checked at compile time (statically) should be. But it doesn't replace explicit testing at run-time.
A whole range of bugs can come from parameters not having the right value at run-time under some conditions, conditions that can't be caught with static analysis. I even think this is what happens most often with bugs in safety-critical software. Teams use very good and expensive static analysis tools, and yet the bugs usually come from parameters getting unexpected values / or a combination of them not being properly checked.

Parameters check can include operations on them. For instance, in some function you could be passing a set of parameters that all are in their respective expected range, but some operation on them would maybe also need to be within some specific range. Not always possible to do this in a purely static way. That's where added "contracts" become necessary. Again, some languages have native support for that, others not, but it's still easy to implement manually.

Also note that any explicit test that can be fully asserted at compile time will usually be optimized out by a decent compiler, so usually only the ones that require to be executed at run-time will be in the resulting binary anyway. You thus get no time penalty when some test can be determined statically as always true or always false.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf