XC8: .asm to C ( i.e. Lowering One's Expectations)

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
Anyway, in this particular case, I was looking for a way to have the compiler automatically allocate an array with one element per state.

Oh well.
 

nsaspook

Joined Aug 27, 2009
16,342
It has everything to do with enums. It tells the compiler the range of accepted values a variable has. This give you some power to avoid some kinds of programming errors.

An integer doesn't do this.
Sure, that's why I use them too but that accepted value restriction is IMO not the root of your problem here.
 

WBahn

Joined Mar 31, 2012
32,928
It's kludge that as you say, works with limitations. In C the initial enumerator always has the value zero and the value of each subsequent enumerator is one greater than the previous enumerator.
Not true.

That's the behavior ONLY if you choose to let the compiler assign ALL of the enumeration values.

Code:
#include <stdio.h>

enum FLAGS
{
    BIT0 = 1,
    BIT1 = 2,
    BIT2 = 4,
    BIT3 = 8,
    FLAGS_LAST
};

int main(void)
{
    enum FLAGS status;
        
    status = BIT1 | BIT3;
    
    printf("status: %u\n", status);
    printf("FLAGS_LAST = %u\n", FLAGS_LAST);
    
    return 0;
}
In this case, FLAGS_LAST has the value 9 because it is not explicitly assigned a value, and so it is implicitly assigned a value one greater than the prior label.
 

WBahn

Joined Mar 31, 2012
32,928
Obviously.

But I use the warning as thus:

Suppose I have 20 different functions that behave differently depending on the current state. I add a new state to the enum, and need to add the behavior for that state to the 20 functions as an additional case to a switch statement.

A quick compile will warn me if I missed updating one or more functions (happened twice today!).

The alternative is multiple debug cycles trying to figure out where I missed a case, and ensuring I exercise all 20 functions in all possible states.
Are you saying that your compiler will not issue those warnings if you have a default case?

If so, then you have the problem of dealing with values outside set of enumerated values. If not, then I don't see the problem since it will still issue a warning if you don't have a case for the new label.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
Sure, that's why I use them too but that accepted value restriction is IMO not the root of your problem here.
The root of my problem is that I'd like my compiler to assist in any way that's technically possible, like telling me how many enum elements there are when I ask it. The compiler knows the answer. It just doesn't want me to know.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
Are you saying that your compiler will not issue those warnings if you have a default case?

If so, then you have the problem of dealing with values outside set of enumerated values. If not, then I don't see the problem since it will still issue a warning if you don't have a case for the new label.
I don't know. I'll check and report back later today.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
If so, then you have the problem of dealing with values outside set of enumerated values.
The only options are the enumerated values. There are no values outside.

(Assuming the switched variable is type enum and not its integer interpretation. )
 

nsaspook

Joined Aug 27, 2009
16,342
Not true.

That's the behavior ONLY if you choose to let the compiler assign ALL of the enumeration values.

Code:
#include <stdio.h>

enum FLAGS
{
    BIT0 = 1,
    BIT1 = 2,
    BIT2 = 4,
    BIT3 = 8,
    FLAGS_LAST
};

int main(void)
{
    enum FLAGS status;
      
    status = BIT1 | BIT3;
  
    printf("status: %u\n", status);
    printf("FLAGS_LAST = %u\n", FLAGS_LAST);
  
    return 0;
}
In this case, FLAGS_LAST has the value 9 because it is not explicitly assigned a value, and so it is implicitly assigned a value one greater than the prior label.
That's exactly what I said. "the value of each subsequent enumerator is one greater than the previous enumerator". I didn't say _LAST will always be the total number of enumerators.
 

WBahn

Joined Mar 31, 2012
32,928
The only options are the enumerated values. There are no values outside.

(Assuming the switched variable is type enum and not its integer interpretation. )
Not as far as the compiler is concerned (unless your particular compiler is able to impose a much stricter set of limitations).

Consider:
Code:
#include <stdio.h>

enum FLAGS
{
    BIT0 = 1,
    BIT1 = 2,
    BIT2 = 4,
    BIT3 = 8,
    FLAGS_LAST
};

typedef enum FLAGS FLAGS;

void report(enum FLAGS status)
{
    printf("status: %u\n", status);
    switch(status)
    {
        case 0:    printf("No bits set\n"); break;
        case BIT1: printf("BIT 1 only bit set\n"); break;
        case BIT2: printf("BIT 2 only bit set\n"); break;
        case BIT3: printf("BIT 3 only bit set\n"); break;
        case BIT1 | BIT3:  printf("BIT 1 and 2 set\n"); break;
        case 15:   printf("ALL bits set\n"); break;
        case FLAGS_LAST:
        default:
            printf("Something else.\n");
    }
    printf("\n");
}

int main(void)
{
    enum FLAGS status;
       
    printf("FLAGS_LAST = %u\n\n", FLAGS_LAST);

    status = BIT1 | BIT3;
    report(status);

    status = BIT0 | BIT1 | BIT2 | BIT3;
    report(status);

    status = BIT0 * BIT1 * BIT2 * BIT3;       
    report(status);

    status = 42;
    report(status);
   
    return 0;
   
   
}
Which produces:

Code:
FLAGS_LAST = 9

status: 10
BIT 1 and 2 set

status: 15
ALL bits set

status: 64
Something else.

status: 42
Something else.
Using enums to give names to bit masks is very common, so if the resulting variables could only take on the values of individual masks and couldn't take on the values of combinations of them, that would be extremely limiting for these uses.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
Are you saying that your compiler will not issue those warnings if you have a default case?
The warning is still thrown even with a default case.

Overall, this is good (and a little bit surprising).

But that means that, in order for the warnings to be useful for me while using @nsaspooks solution, I'd have to include a case in every switch statement for the n+1 enum element to suppress the warning.
 

WBahn

Joined Mar 31, 2012
32,928
That's exactly what I said. "the value of each subsequent enumerator is one greater than the previous enumerator". I didn't say _LAST will always be the total number of enumerators.
You said "In C the initial enumerator always has the value zero and the value of each subsequent enumerator is one greater than the previous enumerator."

I provided a counter example in which both of these claims are false.

The initial enumerator did NOT have a value of zero -- it had a value of 1.

Each subsequent enumerator was NOT one greater than the previous one. The second enumerator had a value of 2, but the next one had a value of 4, and the one after that had a value of 8. Neither of these is one greater than the previous enumerator.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
Not as far as the compiler is concerned (unless your particular compiler is able to impose a much stricter set of limitations).

Consider:
Code:
#include <stdio.h>

enum FLAGS
{
    BIT0 = 1,
    BIT1 = 2,
    BIT2 = 4,
    BIT3 = 8,
    FLAGS_LAST
};

typedef enum FLAGS FLAGS;

void report(enum FLAGS status)
{
    printf("status: %u\n", status);
    switch(status)
    {
        case 0:    printf("No bits set\n"); break;
        case BIT1: printf("BIT 1 only bit set\n"); break;
        case BIT2: printf("BIT 2 only bit set\n"); break;
        case BIT3: printf("BIT 3 only bit set\n"); break;
        case BIT1 | BIT3:  printf("BIT 1 and 2 set\n"); break;
        case 15:   printf("ALL bits set\n"); break;
        case FLAGS_LAST:
        default:
            printf("Something else.\n");
    }
    printf("\n");
}

int main(void)
{
    enum FLAGS status;
     
    printf("FLAGS_LAST = %u\n\n", FLAGS_LAST);

    status = BIT1 | BIT3;
    report(status);

    status = BIT0 | BIT1 | BIT2 | BIT3;
    report(status);

    status = BIT0 * BIT1 * BIT2 * BIT3;     
    report(status);

    status = 42;
    report(status);
 
    return 0;
 
 
}
Which produces:

Code:
FLAGS_LAST = 9

status: 10
BIT 1 and 2 set

status: 15
ALL bits set

status: 64
Something else.

status: 42
Something else.
Using enums to give names to bit masks is very common, so if the resulting variables could only take on the values of individual masks and couldn't take on the values of combinations of them, that would be extremely limiting for these uses.
Ok. But bad programming practice.

Either use the enum labels or don't. Mixing is, IMHO, to be avoided.

(BTW, your example is one of the reasons I like sets in Pascal.)
 

nsaspook

Joined Aug 27, 2009
16,342
You said "In C the initial enumerator always has the value zero and the value of each subsequent enumerator is one greater than the previous enumerator."

I provided a counter example in which both of these claims are false.

The initial enumerator did NOT have a value of zero -- it had a value of 1.

Each subsequent enumerator was NOT one greater than the previous one. The second enumerator had a value of 2, but the next one had a value of 4, and the one after that had a value of 8. Neither of these is one greater than the previous enumerator.
You're right, I slipped, should have said default of zero. The default behaviour is what my _LAST kludge uses in my example code.
 

WBahn

Joined Mar 31, 2012
32,928
The warning is still thrown even with a default case.

Overall, this is good (and a little bit surprising).

But that means that, in order for the warnings to be useful for me while using @nsaspooks solution, I'd have to include a case in every switch statement for the n+1 enum element to suppress the warning.
I think you should still have a default case, in which case (no pun intended) you can just have the last element flow into it.

I think it would be worth a test on your compiler to see whether or not you can produce values in your switch variable that are not in the list of enumerated values. You might try my bitmask example. My guess is that you can and that the compiler is not able to enforce the restriction of the values to the enumerated list, and that this can only be done by coding discipline.
 

WBahn

Joined Mar 31, 2012
32,928
Ok. But bad programming practice.

Either use the enum labels or don't. Mixing is, IMHO, to be avoided.

(BTW, your example is one of the reasons I like sets in Pascal.)
In a real bitmasking application, you might have stuff like "case BIT3 | BIT2:", but you would NOT have a "case 12:" even though they are equivalent.

The point is that in common use cases for enumerations, the cases can be formed as combinations of defined enumeration labels, yielding perfectly reasonable, useful, and maintainable values that lie outside the enumerated labels that serve as the building blocks.

My biggest complaint about C enumerations is that (like most things in C), the notion of any kind of reasonable namespace is completely absent.

Code:
enum FLAGS
{
    CENTERED_X = 1,
    CENTERED_Y = 2,
    COLORED = 4,
    ALPHA = 8,
};

typedef enum FLAGS FLAGS;

enum ADJUSTMENTS
{
    ALPHA, BETA, GAMMA
};

typedef enum ADJUSTMENTS ADJUSTMENTS;
Ideally, if I have a variable of type FLAGS and I set it equal to ALPHA, it should have the value of 8, while if I set a variable of type ADJUSTMENTS equal to ALPHA it should take on the value of 0.

But enums are mostly just a way of assigning values to labels and they have no context, do the above code will throw an error because ALPHA is being redefined.

This puts the burden on the programmer to ensure that their enumeration labels are unique from all other labels, past, present and future. The common way of doing this is to prefix each label with the name of the enumeration, but that can detract from the writability and readability of the code (although, in some respects, the readability and maintainability are improved, at the expense of the 'cleanliness' of the code).
 

WBahn

Joined Mar 31, 2012
32,928
Something I wouldn't do. Rather:

C:
switch (x)
{
    case BIT3:
    case BIT2:
        do something...
        break;
       
    ...
}
Except that that isn't the same logic at all.

Your code requires that x be either EXACTLY equal to BIT2 or exactly equal to BIT3. If BOTH bits are set, your code will not do something.

The code you are trying to match does that case when X is exactly equal to having BOTH of those bits set. If only one of them is set, it won't match.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
Except that that isn't the same logic at all.

Your code requires that x be either EXACTLY equal to BIT2 or exactly equal to BIT3. If BOTH bits are set, your code will not do something.

The code you are trying to match does that case when X is exactly equal to having BOTH of those bits set. If only one of them is set, it won't match.
Your argument really is a straw man. In your example, it will also not match if BIT2 and BIT3 are set, but also BIT 1 and/or BIT4.

If you really mean BIT2 and BIT3 set exclusively (which is not apparent from the style or language), then just use the number (or label) for 12 (or, even better, b'1100'!). Otherwise, you're asking for trouble.

Please show me an actual real-world example of someone who programs like this. I'll know not to hire him.
 

WBahn

Joined Mar 31, 2012
32,928
Your argument really is a straw man. In your example, it will also not match if BIT2 and BIT3 are set, but also BIT 1 and/or BIT4.
I know it will not match if one of the other bits is also set -- that is why I very explicitly used the term "exactly".

But how is it a straw man? You offered code that you said you would use in place of it, and I pointed out that your code would not behave the same. Nothing straw about that. Trying to change the discussion to one about whether the meaning of the code is or is not obvious to deflect attention from the fact that the offered equivalent alternative is simple not equivalent is the straw man.

If you're doing bit-banging, there are almost always additional masking steps involved to isolate the specific bits of interest and the logic often doesn't lend itself to using switch statements. But the points being made are primarily about the behaviors of enumerations. I'm still not convinced that your claim that defining an enumeration somehow prevents values outside the defined set from being assigned to variables of that type is correct. I know that in general it is not, but I don't know whether some compiler, such as the XC8 you are usually talking about, does or not.

If you really mean BIT2 and BIT3 set exclusively (which is not apparent from the style or language), then just use the number (or label) for 12 (or, even better, b'1100'!). Otherwise, you're asking for trouble.

Please show me an actual real-world example of someone who programs like this. I'll know not to hire him.
Using the number instead of the label is extremely poor style. Also, in standard C, b'1100' is not defined. That is a compiler extension that risks locking the code to that compiler, which may or may not be undesirable.

While in this particular example, the name BIT2 implies a specific meaning, in general the bit assignments are essentially arbitrary.

Consider the following:

Code:
#include <stdio.h>

enum VALVES
{
    OXYGEN   = 1<<0,
    NITROGEN = 1<<1,
    ARGON    = 1<<2,
    CO2      = 1<<3,
    METHANE  = 1<<4,
    HELIUM   = 1<<5,

};
typedef enum VALVES VALVES;

#define NOBLE (HELIUM + ARGON)

void noble_content(VALVES mix)
{
    switch (mix & NOBLE)
    {
        case ARGON:
        case HELIUM:
  
            printf("Mixture contains one noble gas\n\n");
            break;
  
        case ARGON + HELIUM:
  
            printf("Mixture contains two noble gases\n\n");
            break;
  
        default:
  
            printf("Mixture doesn't contain either noble gas.\n\n");
    }
}

int main(void)
{
    enum VALVES mixture;
  
    mixture = METHANE + ARGON + OXYGEN;
    noble_content(mixture);
  
    mixture = METHANE + CO2 + OXYGEN;
    noble_content(mixture);

    mixture += HELIUM;
    noble_content(mixture);
      
    mixture += ARGON;
    noble_content(mixture);
      
    return 0;
}

If I code the switch statement with literal values, as you suggest, there is a high probability that I'm going to get at least one of them wrong. I would also find it MUCH harder to read and correctly interpret those constant values. I would also suggest that it would be far less maintainable and much more prone to the introduction of errors down the road.

If I were to decide to redefine the enumeration to put the combustible cases at the low end, the noble gases at the high end, and the other gases in the middle, that's the only change I would have to make -- the rest of the code would not need to be touched. If management then came in and said that the contract requires that the gases be in alphabetical order, or in order of molecular weight, or whatever, all I have to change is the enumeration and everything else updates to reflect the change automatically. If I were to add a new noble gas, such as xenon, it would be pretty easy to do -- add XENON to the enum with an unused bit assignment, add XENON to the #define for NOBLE, and update the switch statement. Though at some point pretty quickly you would be better off using a function that returns the number of bits that are set in a value instead of a switch statement for this particular logic.

If you would refuse to hire a person that wrote code this way, but instead would prefer to hire the person that litters their code with magical mystery numbers, well.... that's certainly your perogative.
 
Last edited:
Top