NZD
NZD

Reputation: 1970

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

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?

Upvotes: 0

Views: 160

Answers (4)

John Bollinger
John Bollinger

Reputation: 181008

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.

Upvotes: 1

chux
chux

Reputation: 154245

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.

Upvotes: 2

0___________
0___________

Reputation: 67747

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

Upvotes: 0

jxh
jxh

Reputation: 70472

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); 

Upvotes: 2

Related Questions