0

I'd like to initialise a variable from a structure of a different type. This works fine, if I do this inside a function, but gives error initializer element is not constant if the variable is defined outside a function. I am using gcc.

Example code:

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>

typedef struct
{
    uint8_t enable : 1;
    uint8_t aboveLevel : 1;
    uint8_t isReceiving : 1;
    uint8_t output : 5;
} busy_settings_t;

#define INITVAR1 *((uint8_t *) (& \
        (busy_settings_t const[]) \
        { \
            { \
                .enable = 0, \
                .aboveLevel = 1, \
                .isReceiving = 0, \
                .output = 1, \
            }, \
        } \
    ))

uint8_t testfn1(void)
{
    uint8_t test = INITVAR1;
    return (test);
}

#if (0)
uint8_t testvar1 = INITVAR1;
#else
uint8_t testvar1 = 0xff;
#endif

int main(int argc, char * argv[])
{
   printf("testfn1:%02x\n", testfn1()); 
   printf("testvar1:%02x\n", testvar1); 
   return (0);
}

In function testfn1, I can initialise a variable from an anonymous structure.

But it doesn't work if I initialise a global variable. Replace #if (0) with #if (1) to see the error.

$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
$ gcc cast_struct_init.c -o csi && ./csi
testfn1:0a
testvar1:ff

Error:

$ gcc cast_struct_init.c -o csi && ./csi
cast_struct_init.c:13:22: error: initializer element is not constant
     #define INITVAR1 *((uint8_t *) (& \
                      ^
cast_struct_init.c:32:24: note: in expansion of macro ‘INITVAR1’
     uint8_t testvar1 = INITVAR1;
                        ^~~~~~~~

How can I make this work?

NZD
  • 1,780
  • 2
  • 20
  • 29
  • Aside: Why `&` in `#define INITVAR1 *((uint8_t *) (& \ (busy_settings_t const[]) ...`? – chux - Reinstate Monica May 25 '21 at 23:16
  • This is one of many possible pitfalls of trying to use bitfields this way. Another is that your "type pun" of casting and dereferencing a pointer may violate strict aliasing and causes undefined behavior. Consider switching to masks: `#define INITVAR1 ((0 << 0) | (1 << 1) | (0 << 2) | (1 << 3))` Or macros for the various bits / masks. – Nate Eldredge May 25 '21 at 23:23
  • 1
    Perhaps `#define BUSY_SETTINGS_INIT(o,i,a,e) ((o)<<3 | (i)<<2 | (a)<<1 | (e)) #define INITVAR1 BUSY_SETTINGS_INIT(1,0,1,0)`? – chux - Reinstate Monica May 25 '21 at 23:24
  • ANSI C does not know compound literals which were introduced later standards. – 0___________ May 25 '21 at 23:25
  • @chux-ReinstateMonica The '&' is needed because you can't typecast a structure to a variable, but you can typecast pointers. I take the address of my structure (using '&'), cast it to a uint8_t pointer and then take its value (using the '*' operator). – NZD May 25 '21 at 23:47
  • @NZD An array object in an expression will decay to a value equal to type and value of the address of its first member. – jxh May 25 '21 at 23:51
  • @jxh How does 'decay' work? My compiler doesn't like: `uint16_t const test2 = *((uint16_t const[]){144, 160, 176, 440, 455, 470});` It gives error: `initializer element is not constant` – NZD May 26 '21 at 01:02
  • @NZD "you can't typecast a structure to a variable" is true, yet `(busy_settings_t const[]) { ... }` is an array. Using the `&` get the address of the array. Without it. the array converts to the type of the address of the first element. Same address yet different type. IAC, `(uint8_t *)` changes the the same type. – chux - Reinstate Monica May 26 '21 at 01:02
  • Do note that the size of your structure type is not guaranteed by the language standard (it may be wider than 8 bits), nor is the order in which the bits appear in the addressable storage unit containing them, nor is the position of the padding if said addressable storage unit is wider than 8 bits. – John Bollinger May 26 '21 at 01:33
  • @JohnBollinger I always add compile-time asserts to check that the sizes match and I also use packed structs to prevent issues with serialising data. – NZD May 26 '21 at 02:15
  • @NZD, even ignoring that all implementations of structure packing are extensions, as is using type `uint8_t` for bitfields, there remains the issue that the order of the bitfields in their associated storage unit is unspecified. They may be arranged from least to most significant position or most to least. – John Bollinger May 26 '21 at 02:25
  • My general advice is to avoid bitfields whenever possible. Their details are *much* less specified than people tend to assume. – John Bollinger May 26 '21 at 02:27

4 Answers4

2

If you want to continue using a compound literal on a structure, don't try to cast it into something else. To achieve the type punning, I would suggest using a union. Initializing a global with a compound literal is not supported by the C Standard, but you can use the designated initializer syntax to initialize the struct members. (Since you are using designated initializers, I will assume you actually mean C.2011 or higher, and not C.1989).

Here's a suggestion:

typedef union
{
    struct {
        uint8_t enable : 1;
        uint8_t aboveLevel : 1;
        uint8_t isReceiving : 1;
        uint8_t output : 5;
    };
    uint8_t all_flags;
} busy_settings_t;

#define INITVAR1_FIELDS \
            { \
                .enable = 0, \
                .aboveLevel = 1, \
                .isReceiving = 0, \
                .output = 1, \
            }

#define INITVAR1 ( \
        (busy_settings_t) \
        { \
            INITVAR1_FIELDS, \
        } \
    ).all_flags

Then, when initializing your global:

busy_settings_t testvar1 = INITVAR1_FIELDS;

To get the type punned value, access all_flags.

   printf("testvar1:%02x\n", testvar1.all_flags); 
jxh
  • 69,070
  • 8
  • 110
  • 193
  • You cant in ANSI-C as ANSI-C does not have compound literals or uint8_t bitfields. This answer foes not reflect the question **`ANSI C: How to initialise ...`*** – 0___________ May 25 '21 at 23:37
  • Since the OP intended to use designated initializers, I will assume intent is C.2011, not C.1989. – jxh May 25 '21 at 23:49
  • Unfortunately, this doesn't work for me. The variable type has to be uint8_t. The example code is only part of the picture to keep it simple. I use the uint8_t value to initialise a member of a structure that describes configuration items that are stored in flash as serialised data i.e. a string of uint8_t items. – NZD May 26 '21 at 00:01
  • @NZD It was a suggestion based on the question as asked. You are free to ask a new question with a more refined description of your problem. – jxh May 26 '21 at 00:06
  • But, if you still intend to use designated initializers to create your constant values, you will probably need to use a code generator to generate intermediate C code to create actual constants based on your struct initializations. – jxh May 26 '21 at 00:15
2

How can I make this work?

Consider making test a union of the 2 types. Then initialize, read and write it as needed.

#include <stdio.h>
#include <stdint.h>

typedef struct {
  uint8_t enable :1;
  uint8_t aboveLevel :1;
  uint8_t isReceiving :1;
  uint8_t output :5;
} busy_settings_t;

typedef union {
  busy_settings_t b;
  uint8_t u8;
} busy_settings_u;

#define INITVAR1 {.enable = 0, \
    .aboveLevel = 1, \
    .isReceiving = 0, \
    .output = 1}

uint8_t testfn1(void) {
  busy_settings_u test = {INITVAR1};
  return test.u8;
}

busy_settings_u testfn1u(void) {
  busy_settings_u test = {INITVAR1};
  return test;
}

busy_settings_u testvar1 = {INITVAR1};

int main() {
  printf("testfn1 :%02x\n", testfn1());
  printf("testfn1u:%02x\n", testfn1u().u8);
  printf("testvar1:%02x\n", testvar1.u8);
  return 0;
}

Output

testfn1 :0a
testfn1i:0a
testvar1:0a

Bit-fields are always a bit tricky and subject to portability issues. For high portability, avoid them.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

I'd like to initialise a variable from a structure of a different type. This works fine, if I do this inside a function, but gives error initializer element is not constant if the variable is defined outside a function

Yes. Paragraph 6.7.9/4 of the standard specifies that

All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.

As an object declared at file scope, your testvar1 has static storage duration, meaning that it has associated storage and retains its initial or last-stored value for the entire duration of the program. The preceding constraint therefore applies to testvar1's initializer, and because it is a language constraint, conforming implementations must diagnose violations.

Paragraph 6.6/7 specifies that a constant expression appearing in an initializer

shall be, or evaluate to, one of the following:

  • an arithmetic constant expression,
  • a null pointer constant,
  • an address constant, or
  • an address constant for a complete object type plus or minus an integer constant expression.

That poses at least two problems for the initializer you are trying to use. From the standard again:

An arithmetic constant expression

(the only viable option for an initializer for an integer)

shall have arithmetic type and shall only have operands that are integer constants, floating constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and _Alignof expressions.

Operands having structure, array, or pointer type are not permitted, and your initializer contains all of those. Also

Cast operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic types, [...].

You have a pointer to pointer cast, which also is not allowed in an arithmetic constant expression.

Thus, your initializer is not a "constant expression" as that term is defined by the standard. This is what GCC reports.

You might find an implementation that accepts your code regardless, as an extension, but if it conforms to the standard then such an implementation will still warn. And such code would have portability problems, as you have already demonstrated. There is no conforming way to initialize an integer with static storage duration from a structure. The closest I think you can come is to declare and initialize a union, as @chux's and @jxh's answers demonstrate.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • If chux's answer does something significantly different from mine, I am curious to know what. Thanks. – jxh May 26 '21 at 02:34
  • @jxh, chux's declares and initializes an object of union type, using a standard union initializer containing only designators and arithmetic constant expressions. Yours purports to provide an initializer for an integer, using an expression containing the member selection operator (`.`) and an operand of type `busy_settings_t`, a union type, neither of which is allowed in an arithmetic constant expression. Though both involve unions, they aren't really very much alike. – John Bollinger May 26 '21 at 02:40
  • `busy_settings_t testvar1 = INITVAR1_FIELDS;` – jxh May 26 '21 at 02:44
  • My bad, @jxh. Your (unused) definition of the `INITVAR1` macro threw me off. – John Bollinger May 26 '21 at 02:47
  • Yes, it was unused in my answer, but used in the OP's code. – jxh May 26 '21 at 02:50
  • Yes, @jxh, but neither your version nor the OP's is usable *as an initializer*, which is how the OP is trying to use theirs, and is suggested by the name. Your answer would be improved by removing it (though I already upvoted). – John Bollinger May 26 '21 at 02:54
  • I agree, but I felt it was an improvement over the array, cast, dereference expression used for the local variable. – jxh May 26 '21 at 03:17
0

NOTE: Meanwhile, OP has changed the question in such a way that this answer has been invalidated. This answer applies to revision 1 of the question, which was about ANSI-C.

Your whole code is not ANSI-C

  1. uint8_t enable : 1; using this type in bitfields is a gcc extension
  2. ANSI-C does not have compound literals
  3. ANSI-C does not allow subobjects in an initialization

So you need to rewrite the whole snippet to make it ANSI-C compatible

ANSI C: How to initialise a variable from an anonumous structure of a different type

In ANSI-C it is not possible

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
0___________
  • 60,014
  • 4
  • 34
  • 74
  • Thanks for that, I'll change my title. It needs to work in gcc. – NZD May 25 '21 at 23:49
  • Now your answer doesn't match the question as posted :-) – jxh May 25 '21 at 23:52
  • @jxh lovely - isn't it? – 0___________ May 26 '21 at 00:00
  • As far as I can tell, your time spent to help was to describe to the OP the ways in which their question was incorrect and had to be rewritten, based on the assumption they wanted to write their program using a strict interpretation of a 32 year old dialect of C. I still think your answer is useful, though. – jxh May 26 '21 at 00:00
  • So, we currently have two answers, where I assumed the OP didn't really mean ANSI C, but Standard C, and you did not. I think both answers are useful. – jxh May 26 '21 at 00:02