Yuval
Yuval

Reputation: 3433

Is there a way to initialize a `const` `struct` using `const` variables?

I would like to create a struct in C99 that encapsulates a null-terminated string buffer together with the actual string length:

typedef unsigned int uint_t;
typedef unsigned char uchar_t;

typedef struct {
    uchar_t * buffer;
    uint_t length; // excluding null-terminating character
} string_t;

However, I am encountering several difficulties revolving the const-ness of the struct's members. Specifically, when I want to consume a function that accepts such a const struct string_t, and feed it with an initializer with const members, the compiler yells at me.

void show_string_internal(const string_t str) {
    printf("%s", str.buffer);
}
void show_string(const uchar_t * buffer, uint_t length) {
    const string_t str = // <== tricky assignment here
        (const string_t){ buffer, length }; 
    show_string_internal(str);
}
int main() {
    uchar_t message[] = "Hello, world.";
    show_string(message, sizeof(message) - 1);
    return 0;
}

This produces a warning over the highlighted line both in GCC...:

warning: initialization discards 'const' qualifier from pointer target type

... and in Visual Studio 2015:

warning C4090: 'initializing': different 'const' qualifiers

Apparently, I am doing something wrong here. The only way I found to get around this is by declaring:

typedef struct {
    const uchar_t * buffer;
    const uint_t length;
} const_string_t;

But now I have two types instead of one, so I'll need to create handy ways to convert between the two, and I'm creating declarative sugar instead of using language features, so the code is less readable.

So my question is, as the title reads: is there a way to initialize a const struct using const variables for the members? Is there an alternative manner to reach the results I wish to achieve? If not, why not (please include references to official documentation)?

Any help would be appreciated.

Upvotes: 1

Views: 2461

Answers (5)

0___________
0___________

Reputation: 67476

it is just the wrong constantness:

typedef unsigned int uint_t; typedef unsigned char uchar_t;

use the correct type for size. In C it is the size_t

typedef struct {
    const uchar_t * buffer;
    size_t length; // excluding null-terminating character
} string_t;

void show_string(const uchar_t * buffer, size_t length) {
    const string_t str = // <== tricky assignment here
        (string_t){ buffer, length }; 
} 

Upvotes: 0

Yuval
Yuval

Reputation: 3433

As other people have written, the problem has to do with the way the const type qualifier affects the struct aggregate type. All of the struct member values become const themselves. For pointer members, it only means that you may not change the pointer variable so that it would point at anything else, but whatever the pointer points to remains mutable. This is similar to what would happen if you'd declare an int * const variable.

There seems to be no way in C99 to endow const-ness on the targets of indirect (pointer) types that are part of so-called aggregate types. So there is no way to attach a keyword to a struct-type variable to signify that its pointer members should be considered pointers to const types, such as int const *. This is only mentioned by example in both the C99 and C11 standards, at the end of section 6.7.3 (Type Qualifiers).

This answer is based on correspondence in reply to Jean-Baptiste Yunès' answer, between Yuval and PSkocik.

Upvotes: 0

Yury Schkatula
Yury Schkatula

Reputation: 5369

Constness about pointer types should be read "from right to left" (and "const T * buffer" means "pointer to const content" actually, not "const pointer to content" nor "const pointer to const content).

So, you was about to use mutable pointer to const char buffer and use it for const version of structure (asking your const struct having mutable field - this is a reason for the warning shown). In case you plan to get rid of the warning you have to move "const" thing like that:

void show_string(char * const buffer, const int length) { // <== move const to the right of "*"
    const string_t str = // <== tricky assignment here
        (const string_t) {
        buffer, length
    };
    show_string_internal(str);
}

In case you plan to have const buffer itself as well, you have to redefine your string_t structure (or introduce as separate type):

typedef struct {
    const uchar_t * buffer; // <== add "const" here
    uint_t length; // excluding null-terminating character
} string_t;
...
void show_string(const char * const buffer, const int length) { // two "const" clauses there
    const string_t str = // <== tricky assignment here
        (const string_t) {
        buffer, length
    };
    show_string_internal(str);
}

Hope this helps.

Upvotes: 2

Petr Skocik
Petr Skocik

Reputation: 60068

The problem is not at the struct level. The problem is in the initialization of .buffer (unsigned char*) with char const*. If you make .buffer char const*, it'll work without casts, although you will still get warnings with -Wall due to different signedness.

Upvotes: 1

Jean-Baptiste Yun&#232;s
Jean-Baptiste Yun&#232;s

Reputation: 36401

Your struct has a member declared as uchar_t *buffer and you tried to initialize it with a parameter declared as const char *buffer. So the compiler complains because such would remove the presupposed constness of the memory pointed by buffer (such a violation is a logical error). Depending on what you need/want you may:

  1. pass parameter as non-const,
  2. pass as const, but then declare member as const in the structure,
  3. use a copy semantic and allocate some memory to the member and copy values pointed by the parameter to memory pointed by member.

Also be carefull that uchar_t and char may not be the same type...

Upvotes: 0

Related Questions