6

During a recent discussion (see comments to this answer), R.. recommended to never create aliases for pointer-to-const types as you won't be able to deallocate the referenced objects easily in a conforming C program (remember: free() takes a non-const pointer argument and C99 6.3.2.3 only allows conversions from non-qualified to qualified).

The C language obviously assumes the existance of an owner for any allocated object, ie someone somewhere has to store a non-const pointer to the object, and this someone is responsible for deallocation.

Now, consider a library allocating and initializing objects which are non-modifyable from user-space code, so function calls always return const-qualified pointers.

Clearly, the library is the owner of the object and should retain a non-const pointer, which is somewhat silly as the user already supplies a perfectly valid, but const copy of the pointer on each library call.

To deallocate such an object, the library has to discard the const qualifier; as far as I can tell, the following

void dealloc_foo(const struct foo *foo)
{
    free((void *)foo);
}

is valid C; it would only be invalid if the foo parameter were additionally restrict-qualified.

However, casting away const seems somewhat hack-ish.

Is there another way aside from dropping the const from all return values of library functions, which would lose any information about object mutability?

Community
  • 1
  • 1
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • See my answer. This use of `const` is simply wrong. – R.. GitHub STOP HELPING ICE Oct 23 '10 at 08:14
  • 1
    If you hadn't followed the bad advice in that other question, you wouldn't be worried about some other code mucking around inside your data structure to the point you felt compelled to apply `const`. The correct level of abstraction for a handle is that your library, and only your library, can use it to find the associated object internal data. Users of the library **should not know** whether the handle is a memory address (aka pointer), index into one of your data structures, or some other key, just that it is a value produced and used by your library and meaningful only in that context. – Ben Voigt Dec 05 '10 at 01:19

3 Answers3

4

I don't read the same thing in 6.3.2.3. That paragraph is about conversions that happen implicitly and that don't need a casts. So an implicit conversion from a pointer-to-const object may be not permitted, but this says nothing about explicit casts.

Casts are handled in 6.5.4, and I don't see anything that would constrain you from a cast of any pointer-to-const that is by itself not const qualified with (void*). In the contrary it says

Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.

So I read there that if you do things explicitly, they are permitted.

So I think the following is completely valid

char const *p = malloc(1);
free((void*)p);
What 6.5.4 forbits would be the following char *const p = malloc(1); free((void*)p); /* expression of cast is const qualified */

As a side note, for your second line of thoughts, a library that returns a pointer-to-const qualified object that then is to be placed in the responsibility of the caller, makes not much sense to me. Either

  • the library returns a pointer to an internal object, then it should qualify it pointer-to-const to have some weak protection that the caller doesn't change it, or
  • the library returns a freshly allocated object that falls in the responsibility of the caller. Then it shouldn't care much of whether the caller changes it, so it may return a pointer-to-unqualified object. If the caller then wants to ensure that the contents is not accidentally overwritten or something he might assign it to a const pointer, for his use.
Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • you're right, casting away `const` on function arguments is perfectly valid; I thought of a pointer-to-`const` parameter as a promise to the compiler that the function won't modified the pointed-to object, allowing additional optimizations on the calling side; however, this is only true if the parameter is `restrict`-qualified as well; a plain pointer-to-`const` parameter has no benefit over a non-`const` version in terms of optimization – Christoph Oct 23 '10 at 07:43
  • @schot: ok, I'll try to rephrase. – Jens Gustedt Oct 23 '10 at 14:25
  • 2
    `char *const p = malloc(1); free(p);` is perfectly legal. The `const` modifier on `p` doesn't affect the pointed-to data at all, and doesn't need to be removed before calling `free`. – Ben Voigt Dec 05 '10 at 01:25
  • Spot on Ben Voigt, one remembers this through extensive use of malloc only :) – Matt Joiner Dec 05 '10 at 13:43
2

If the object is truly immutable, why give the user the pointer to it and risk an opportunity of corrupt object? Keep the object address in the table, return an opaque handle (an integer table index?) to the user and accept that handle in your library routines.

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • Not using a library or even an OS and running directly on the hardware is even more efficient :) – Nikolai Fetissov Oct 22 '10 at 19:38
  • also, using an address table either limits the number of allocatable objects or adds complexity for managing table resizing; but yes, this is a valid alternative – Christoph Oct 22 '10 at 19:39
  • 1
    Or just cast or `typedef` the address to some `my_secret_handle_type` and return it. – Nikolai Fetissov Oct 22 '10 at 20:00
  • Yes, if you're going to obscure the reference to p, might as well return (uintptr_t)p, adding an indirection table provides no additional benefit. – Keith Randall Oct 22 '10 at 21:20
  • I can think of some benefits a layer of indirection can provide - permissions gates, virtual dispatch, state changes, whatever. The table itself does not have to be static either. – Nikolai Fetissov Oct 22 '10 at 22:54
  • Sure, there are plenty of reasons to use an indirection table. Solving the OP's problem isn't one of them. – Keith Randall Oct 25 '10 at 17:44
  • Very bad idea. Using a table makes your implementation either (1) non-thread-safe, or (2) dependent upon a particular thread implementation and impossible to use without it. Using pointers means you don't have to care about thread-safety; `malloc` already cared about it for you. – R.. GitHub STOP HELPING ICE Nov 14 '10 at 17:14
2

If you return a const-qualified pointer, the semantics are that the caller of your function is not permitted to modify the object in any way. That includes freeing it, or passing it to any function in your library that would in turn free it. Yes I'm aware that you could cast away the const qualifier, but then you're breaking the contract that const implies.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • that's what I came to think as well, but actually, there is no such contract as long as the pointer isn't `restrict`-qualified; as far as I can tell, modifying a non-`const` object (which all allocated objects are) through a pointer-to-`const` by discarding the qualifier is a well-defined operation; this also means that in the past, I should have used `restrict` far more liberally... – Christoph Oct 23 '10 at 09:13
  • 1
    Read the last sentence of my answer. As far as I can tell, the cast is valid C and a compiler cannot reject it. But there are lots of things that are valid C but violate the implied contract of `const`. For instance I could make a `strcpy` function that takes `const char *` for both its source and destination arguments and casts away the `const` from the destination to write to it. As long as you only pass modifiable objects, this will work, but it's idiotic. Wherever a `const` pointer is passed, pointers to nonmodifiable objects should be valid in any context where the pointer gets used. – R.. GitHub STOP HELPING ICE Oct 23 '10 at 19:29
  • but this contract is not enforced by the C language, ie the contract you speak of is not between you and the compiler (as is the case when declaring `const` objects or `restrict`-qualifying pointers), but between you and the users of your code; this means anything goes as long as it's properly documented; also, in case of opaque types (ie the object definition is hidden), the user can't pass an invalid value to the custom deallocation function by mistake, so I don't really see your point... – Christoph Oct 23 '10 at 20:25
  • I agree, but I still consider it an extremely bad practice to use `const` in ways contrary to its normal contractual meaning, and pretty much equivalent to my `strcpy` example. – R.. GitHub STOP HELPING ICE Oct 24 '10 at 01:32