46

I would like to allocate a structure on the heap, initialize it and return a pointer to it from a function. I'm wondering if there's a way for me to initialize const members of a struct in this scenario:

#include <stdlib.h>

typedef struct {
  const int x;
  const int y;
} ImmutablePoint;

ImmutablePoint * make_immutable_point(int x, int y)
{
  ImmutablePoint *p = (ImmutablePoint *)malloc(sizeof(ImmutablePoint));
  if (p == NULL) abort();
  // How to initialize members x and y?
  return p;
}

Should I conclude from this that it is impossible to allocate and initialize a struct on the heap which contains const members?

5 Answers 5

64

Like so:

ImmutablePoint *make_immutable_point(int x, int y)
{
  ImmutablePoint init = { .x = x, .y = y };
  ImmutablePoint *p = malloc(sizeof *p);

  if (p == NULL) abort();
  memcpy(p, &init, sizeof *p);

  return p;
}

(Note that unlike C++, there is no need to cast the return value of malloc in C, and it is often considered bad form because it can hide other errors).

Sign up to request clarification or add additional context in comments.

9 Comments

Note that the use of .x and .y in the init initialization is C99, you of course can use unamed entries if your compiler does not support it.
Standard says: (6.7.3) If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined. and in undefined behaviour section: An attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type (6.7.3). That would make this example exhibit ub. What is your opinion?
@self.: The object that is pointed to by the pointer returned here is never actually defined, since it is created by malloc(). Consider that p could just as well be defined as void *p; instead, with both instances of sizeof *p changed to sizeof init.
@Ephemera: Yes, it can - gcc on x86-64 using the lowest level of optimisation will compile the above function into one that does not call memcpy(). That said, once you're calling malloc(), the memcpy() is not likely to be a concern.
@kevinarpe: It does not matter, because sizeof explicitly does not evaluate its argument, so the behaviour of the expression is still defined.
|
12

If this is C and not C++, I see no solution other than to subvert the type system.

ImmutablePoint * make_immutable_point(int x, int y)
{
  ImmutablePoint *p = malloc(sizeof(ImmutablePoint));
  if (p == NULL) abort();

  // this 
  ImmutablePoint temp = {x, y};
  memcpy(p, &temp, sizeof(temp));

  // or this
  *(int*)&p->x = x;
  *(int*)&p->y = y;

  return p;
}

1 Comment

If this is C, casting the return value of malloc is unnecessary and redundant.
2

If you insist on keeping the const in the structure, you are going to have to do some casting to get around that:

int *cheat_x = (int *)&p->x;
*cheat_x = 3;

2 Comments

Doesn't that invoke undefined behaviour?
If you allocated the memory, I think it is fine.
1

I like caf's approach, but this occured to me too

ImmutablePoint* newImmutablePoint(int x, int y){ 
   struct unconstpoint {
      int x;
      int y;
   } *p = malloc(sizeof(struct unconstpoint));
   if (p) { // guard against malloc failure
      *p.x = x;
      *p.y = y;
   }
   return (ImmutablePoint*)p;
}

3 Comments

Sorry, I don't see the const here. Can you please explain?
@KCArpe: I allocate and assign a struct that is identical in structure to ImmutablePoint but is mutable, then cast the pointer in the return statement so that the mutable version is never visible to the user. It's a fairly standard abuse of casting to "break" the type system. The disadvantage of this relative caf's solution is that it is not DRY: if someone edits ImmutablePoint I need to edit unconstpoint as well.
also, to avoid the issue with someone changing the size of ImmutablePoint, it may be best to add an assert(sizeof(ImmutablePoint) == sizeof(struct uconstpoint)); though the compiler is also allowed to make them different.
1

To expand on the accepted answer, the reason why this is allowed:

ImmutablePoint init = { .x = x, .y = y };
ImmutablePoint *p = malloc(sizeof *p);
memcpy(p, &init, sizeof *p);

Is because memory returned by malloc (or more accurately, the object returned) has no effective type and is therefore allowed to be written to. Then once memcpy is used to copy in the source object, the effective type of the allocated object becomes ImmutablePoint.

This is spelled out in section 6.5p6 of the C standard:

The effective type of an object for an access to its stored value is the declared type of the object, if any. 87) If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.


87) Allocated objects have no declared type.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.