cembeddedpicpic24

How can I return a pointer to registers with different types


I would like to write a function which returns a pointer to either one of two special function registers (SFRs). These SFRs have different types, but their types are identical except for their names:

#define SFRxType SFR0Type

typedef struct SFR0Type {
    union {
        struct {
            uint16_t var0;
            uint16_t var1;
        };
        struct {
            uint32_t var;
        };
    };
} SFR0Type;
extern volatile SFR0Type SFR0 __attribute__((__sfr__));

typedef struct SFR1Type {
    union {
        struct {
            uint16_t var0;
            uint16_t var1;
        };
        struct {
            uint32_t var;
        };
    };
} SFR1Type;
extern volatile SFR1Type SFR1 __attribute__((__sfr__));

(The __sfr__ attribute tells the compiler the variable address is set by the linker script.)

These typedefs reside in a header I do not own, so I cannot change them.

My function looks like this:

static volatile SFRxType* get_sfr_ptr(uint16_t select) {
    return select ? &SFR1 : &SFR0;
}

However, this produces a compiler warning: warning: pointer type mismatch in conditional expression.

How can I write a function like this without generating a warning, given the above constraints?


Solution

  • The ?: isn't really suitable since it requires the 2nd and 3rd operands to be of types that are either compatible or can be made compatible by implicit conversion. If using that operator you have to cast the operand which isn't of the correct type and from there dive down into this "language lawyer" rabbit hole (C17 6.5.15):

    Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type;

    Unfortunately these 2 structs here are not compatible because someone had the bad idea to use different struct tags for them. If not for those otherwise 100% superfluous tags, they would have been compatible even if typedef names were different.

    So the best option is probably to forget all about implicit conversions and composite types and instead use if. Keep it simple. And do explicit casts no matter which type that's picked:

    static volatile SFRxType* get_sfr_ptr(uint16_t select) {
        if(select != 0)
          return (volatile SFRxType*)&STF1;
    
        return (volatile SFRxType*)&STF0;
    }
    

    But please note that this returned pointer will still have to get cast to its correct type before use - again because the structs are not compatible.