43

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?

user268344
  • 839
  • 1
  • 8
  • 10

4 Answers4

62

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

caf
  • 233,326
  • 40
  • 323
  • 462
  • 8
    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. – Trent Feb 08 '10 at 00:52
  • 1
    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? – this May 23 '14 at 12:28
  • 3
    @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`. – caf May 25 '14 at 13:10
  • 1
    @caf Yes your version is correct, my comment was made before I came to that realization. As far as I know this is the only legal way to initialize a const type with allocated memory. – this May 25 '14 at 16:31
  • Can this memcpy be optimised away by the compiler? I can see this getting expensive for repeated allocations of large structs. – Ephemera Dec 26 '16 at 05:51
  • 3
    @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. – caf Dec 26 '16 at 11:45
  • @caf: As I understand, before expression `malloc(sizeof *p)`, the variable `p` is undefined. Instead, should it be `malloc(sizeof init)` or `malloc(sizeof ImmutablePoint)`? – kevinarpe Jan 30 '17 at 05:15
  • 5
    @kevinarpe: It does not matter, because `sizeof` explicitly does not evaluate its argument, so the behaviour of the expression is still defined. – caf Jan 30 '17 at 05:58
  • @caf True, except if used with a variable length array (in C99). – martinkunev Jun 06 '19 at 19:15
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;
}
John Knoeller
  • 33,512
  • 4
  • 61
  • 92
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;
R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
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;
}
Community
  • 1
  • 1
dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
  • 1
    Sorry, I don't see the const here. Can you please explain? – kevinarpe Nov 29 '10 at 09:00
  • 1
    @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](http://en.wikipedia.org/wiki/Don%27t%5Frepeat%5Fyourself): if someone edits `ImmutablePoint` I need to edit `unconstpoint` as well. – dmckee --- ex-moderator kitten Nov 29 '10 at 18:52
  • 1
    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. – Ryan Haining Aug 30 '13 at 20:49