Re: Why is the kfree() argument const?

Previous thread: What's in linux1394-2.6.git? by Stefan Richter on Thursday, January 17, 2008 - 2:07 pm. (3 messages)

Next thread: [PATCH 3/20] UML - SMP locking commentary by Jeff Dike on Thursday, January 17, 2008 - 2:40 pm. (1 message)
From: Linus Torvalds
Date: Thursday, January 17, 2008 - 2:25 pm

No. It's the *pointer* that is no longer valid.

There's definitely a difference between "exists and is changed" and 

It's not a change to the data behind it, it's a change to the *metadata*. 

No, it's why I'm right.

"kmalloc/kfree" (or any memory manager) by definition has to play games 
with pointers and do things like cast them. But the users shouldn't need 

No. 

You are continuing to make the mistake that you think that "const" means 
that the memory behind the pointer is not going to change.

Why do you make that mistake, when it is PROVABLY NOT TRUE!

Try this trivial program:

	int main(int argc, char **argv)
	{
	        int i;
	        const int *c;
	
	        i = 5;
	        c = &i;
	        i = 10;
	        return *c;
	}

and realize that according to the C rules, if it returns anything but 10, 
the compiler is *buggy*.

The fact is, that in spite of us having a "const int *", the data behind 
that pointer may change.

So it doesn't matter ONE WHIT if you pass in a "const *" to "kfree()": it 
does not guarantee that the data doesn't change, because the object you 
point to has other pointers pointing to it.

This isn't worth discussing. It's really simple: a conforming program 
CANNOT POSSIBLY TELL whether "kfree()" modified the data or not. As such, 
AS FAR AS THE PROGRAM IS CONCERNED, kfree() takes a const pointer, and the 
rule that "if it can be considered const, it should be marked const" comes 
and says that kfree() should take a const pointer.

In other words - anythign that could ever disagree with "const *" is BY 
DEFINITION buggy.

It really is that simple. 

		Linus
--

From: David Schwartz
Date: Thursday, January 17, 2008 - 3:28 pm

The pointer is no longer valid because the object it pointed to no longer
exists. The pointer is also no longer valid, but that is not the end of the

It doesn't matter what has changed. All that matters is whether this is
something we normally want to happen to a const pointer or whether doing

If you don't like having to cast, don't use 'const'. But if you use 'const',
you have to cast when you mean to do something that you would like to be

No, that's not what it means. It has nothing to do with memory. It has to do

I don't. You do, because you argue 'kfree' can be const because it doesn't
change the memory. The change in the memory is meaningless, the change in

I don't know what you think this example proves. Nobody is arguing that so
long as one const pointer to an object exists, no code anywhere should ever
be able to change it.

All I'm saying is that changing the logical state of an object *through* a
const pointer is unusual. You should need a cast to do this because that's


But that's exactly what doesn't matter. As you've said at least twice now,
it has nothing to do with changing the data. It has to do with changing the
logical state of the object. That's what you're not supposed to do through a


I think you may be the only person in the world who thinks so.

DS


--

From: Linus Torvalds
Date: Thursday, January 17, 2008 - 4:10 pm

Blah. That's just your own made-up explanation of what you think "const"
should mean. It has no logical background or any basis in the C language.

"const" has nothing to do with "logical state".  It has one meaning, and 
one meaning only: the compiler should complain if that particular type is 
used to do a write access.

It says nothing at all about the "logical state of the object". It cannot, 
since a single object can - and does - have multiple pointers to it.

So your standpoint not only has no relevant background to it, it's also 
not even logically consistent.

		Linus
--

From: David Schwartz
Date: Thursday, January 17, 2008 - 5:56 pm

To some extent, I agree. You can use "const" for pretty much any reason.
It's just a way to say that you have a pointer and you would like an error
if certain things are done with it.

You could use it to mean anything you want it to mean. The most common use,
and the one intended, is to indicate that an object's logical state will not


You are the only one who has suggested it has anything to do with changes
through other pointers or in other ways. So you are arguing against only
yourself here.

Nobody has said it has anything to do with anything but operations through

Actually, that is true of your position. On the one hand, you defend it
because kfree does not change the data. On the other hand, you claim that it
has nothing to do with whether or not the data is changed.

The normal use of "const" is to indicate that the logical state of the
object should not be changed through that pointer. The 'kfree' function
changes the logical state of the object. So, logically, 'kfree' should not
be const.

The usefulness of "const" is that you get an error if you unexpectedly
modify something you weren't expected to modify. If you are 'kfree'ing an
object that is supposed to be logically immutable, you should be made to
indicate that you are aware the object is logically immutable.

Simply put, you you have to cast in any case where you mean to do something
that you want to get an error in if you do not cast. I would like to get an
error if I call 'kfree' through a const pointer, because that often is an
error. I may have a const pointer because my caller still plans to use the
object.

Honestly, I find your position bizarre.

DS


--

From: Linus Torvalds
Date: Thursday, January 17, 2008 - 6:15 pm

So why do you complain? 


No, I'm saying that "const" has absolutely *zero* meaning on writes to an 
object through _other_ pointers (or direct access) to the object.

And you're seemingly not understanding that *lack* of meaning.

kfree() doesn't do *squat* to the object pointed to by the pointer it is 
passed. It only uses it to look up its own data structures, of which the 
pointer is but a small detail.


.. and I'm telling you: kfree() does *nothing* conceptually through that 
pointer. No writes, and not even any reads! Which is exactly why it's 
const.

The only thing kfree does through that pointer is to update its own 
concept of what memory it has free.

Now, what it does to its own free memory is just an implementation detail, 
and has nothing what-so-ever to do with the pointer you passed it.

See?

		Linus
--

From: David Schwartz
Date: Thursday, January 17, 2008 - 10:02 pm

Because the object ceases to exist. However, any modification requires write



Nonsense. The 'kfree' function *destroys* the object pointer to by the

It destroys the object the pointer points to. Destroying an object requires

That is not what it does, that is how it does it. What it does is destroy


I now have a much better understanding of what you're saying, but I still
think it's nonsense.

1) An operation that modifies the logical state of an object should not
normally be done through a 'const' pointer. The reason you make a pointer
'const' is to indicate that this pointer should not be used to change the
logical state of the object pointed to.

2) The 'kfree' operation changes the logical state of the object pointed to,
as the object goes from existent to non-existent.

3) It is most useful for 'kfree' to be non-const because destroying an
object through a const pointer can easily be done in error. One of the
reasons you provide a const pointer is because you need the function you
pass the pointer to not to modify the object. Since this is an unusual
operation that could be an error, it is logical to force the person doing it
to clearly indicate that he knows the pointer is const and that he knows it
is right anyway.

I'm curious to hear how some other people on this feel. You are the first
competent coder I have *ever* heard make this argument.

By the way, I disagree with your metadata versus data argument. I would
agree that a function that changes only an object's metadata could be done
through a const pointer without needed a cast. A good example would be a
function that updates a "last time this object was read" variable.

However, *destroying* an object is not a metadata operation -- it destroys
the data as well. This is kind of a philosophical point, but an object does
not have a "does this object exist" piece of metadata. If an object does not
exist, it has no data. So destroying an object destroys the data and is thus
a write/modification ...
From: Chris Friesen
Date: Friday, January 18, 2008 - 8:38 am

I don't think that kfree() itself changes the state of the object.  It 
doesn't call a destructor or anything like that, so the object itself 
must be "inert" before the call to kfree().  That is, at the time of the 
kfree() call the system must have ensured that the object will no longer 
be used by anything.

The call to kfree() is simply bookkeeping--allowing that memory to be 

I have a certain amount of sympathy for this view...it's a fairly 
painless way to reduce the likelihood of errors.  At the same time, I 
don't think I've ever run into this problem myself--is it really all 
that common?

Chris
--

From: Linus Torvalds
Date: Friday, January 18, 2008 - 9:10 am

Here's an idea. Think it through. 

Why don't we need write permissions to a file to unlink it?

Here's a hint: because unlinking doesn't *write* to it. In fact, it 
doesn't read from it either. It doesn't do any access at all to that 
object, it just *removes* it.

Is the file gone after you unlink it? Yes (modulo refcounting for aliasing 
"pointers" aka filenames, but that's the same for any memory manager - 
malloc/free just doesn't have any, so you could think of it as a 
non-hardlinking filesystem).

So you're the one who are speaking nonsense. Making something "not exist" 
is not at all the same thing as accessing it for a write (or a read). It 
is a metadata operation that doesn't conceptually change the data in any 
way, shape or form - it just makes it go away.

And btw, exactly as with kfree(), a unlink() may well do something like 
"disk scrubbing" for security purposes, or cancel pending writes to the 
backing store. But even though it may write (or, by undoing a pending 
write, effectively "change the state") to the disk sectors that used to 
contain the file data, ONLY AN IDIOT would call it "writing to the file". 
Because "the file" is gone. Writing to the place where the file used to be 
is a different thing.

So give it up. You're wrong. Freeing a memory area is not "writing to it" 
or accessing it in *any* manner, it's an operation on another level 
entirely.

			Linus
--

From: David Schwartz
Date: Friday, January 18, 2008 - 1:55 pm

You cannot unlink a file. Given a file, if you were to attempt to unlink it,
what directory would you remove it from?

Unlinking a file is an operation on the directory the file is in, not on the
directory itself. We do need write permissions to the directory.

If you had only a const pointer to the data in the file, you should
definitely not be able to use that pointer to find a directory the file is
in and remove it without clearly indicating you know *exactly* what you're
doing. Given just that 'const' pointer, you're not supposed to be modifying
the data and certainly using that pointer to modify anything logically above

Right. It's an operation on the directory the file is in that might have

The file is gone if and only if the directory was the only thing that needed
the file to exist. A file that is only on one directory "belongs to" that
directory. So write permission to the directory is all that is needed.

What you are arguing is essentially that you should be able to remove a file
from any directory it is in just because you have write access to the file's


I agree with you about that part. I can't understand why you keep thinking
this is where our disagreement lies when I've stated at least three times
that I agree about this. The issue has nothing to do with whether or not
'kfree' modifies the particular bytes pointed to. It has to do with whether
or not 'kfree' is the kind of operation one would normally want to allow on

Nevertheless, it's a modification operation on an object that's not supposed
to be modified.

By the way, I did think of one argument that supports your position: Suppose
you have a reference counted object. You have a 'release reference and free
if zero' function. Should it be 'const'? If not, how can a 'lookup and
reference for read' function return a const pointer to the object?

However, on balance, I think a 'release reference and free if zero' function
that operates on a const pointer is sufficiently unusual that a cast to ...
From: Olivier Galibert
Date: Friday, January 18, 2008 - 10:37 am

Freeing a const pointer is not and has never been unusual.  It happens
all the time for objects whose lifecycle is "initialise at the start,
readonly afterwards", of which names, in particular in the form of
strings, are a large subset.  It also happens in cases of late
deletion on refcounted objects, when the main owner (the one who is
allowed to change the object and has the non-const pointer) has
dropped its reference, but some object needs a readonly instance a
little longer.  Think virtual files in proc, sysfs or friends kept
open after the underlying information source, often a device, is gone.

  OG.
--

From: DM
Date: Friday, January 18, 2008 - 11:06 am

In C++ you can delete a const pointer. Now I know kernel hackers
aren't especially impressed with C++ but maybe someone could look up
the rationale for that design decision (I couldn't find it). It might
shed some light on this discussion.

/DM
--

From: Giacomo Catenazzi
Date: Friday, January 18, 2008 - 1:20 am

And to demostrate that Linus is not the only person
with this view, I copy some paragraphs from C99 rationale
(you can find standard, rationale and other documents
in http://clc-wiki.net/wiki/C_standardisation:ISO )

Page 75 of C99 rationale:

Type qualifiers were introduced in part to provide greater control over optimization. Several
important optimization techniques are based on the principle of "cacheing": under certain
circumstances the compiler can remember the last value accessed (read or written) from a
location, and use this retained value the next time that location is read. (The memory, or
"cache", is typically a hardware register.) If this memory is a machine register, for instance, the
code can be smaller and faster using the register rather than accessing external memory.
The basic qualifiers can be characterized by the restrictions they impose on access and
cacheing:

const          No writes through this lvalue. In the absence of this qualifier, writes may occur
               through this lvalue.

volatile       No cacheing through this lvalue: each operation in the abstract semantics must
               be performed (that is, no cacheing assumptions may be made, since the location
               is not guaranteed to contain any previous value). In the absence of this qualifier,
               the contents of the designated location may be assumed to be unchanged except
               for possible aliasing.

restrict       Objects referenced through a restrict-qualified pointer have a special
               association with that pointer. All references to that object must directly or
               indirectly use the value of this pointer. In the absence of this qualifier, other
               pointers can alias this object. Cacheing the value in an object designated through
               a restrict-qualified pointer is safe at the beginning of the block in which the
               pointer is declared, because no pre-existing aliases may also be used to ...
From: Andy Lutomirski
Date: Friday, January 18, 2008 - 6:53 am

I'd say this implies the exact opposite.  It almost sounds like the 
compiler is free to change:

void foo(const int *x);
foo(x);
printf("%d", x);

to:

void foo(const int *x);
printf("%d", x);
foo(x);

especially if it can prove that the pointer to x doesn't otherwise 
escape or that foo doesn't call anything that could see the pointer (and 
given that gcc has special magical markings for malloc, one way this 
could be "proven" is to have x be some freshly malloced object.

If foo is kfree, then the above transformation is clearly invalid.

(Note that this isn't just a problem for optimizers -- a programmer 
might expect that passing a pointer to a function that takes a const 
pointer argument does not, in and of itself, change the pointed-to 
value.  Given that const certainly does not mean that no one else 
changes the object, I'm not sure what else it could mean.  kfree does 
not have either property, so I'm don't think it makes sense for it to 
take a const argument.)


--

From: Olivier Galibert
Date: Friday, January 18, 2008 - 10:24 am

That's only if neither function has side effects noticeable by the

Most of the time, const pointer arguments means "I won't change the
contents of the object so that you'll notice by reading it in a normal
way afterwards".  That's pretty much what mutable in a variety of
languages (including C++) is about, saying "this field is internal
management stuff not visible from the external interface, so I need to
be able to change it even through const pointers I got as parameters".
Reference counters for copy-on-write setups is the usual example of
use.

In the case of deallocation functions you are not allowed to do
anything through the pointer or its aliases after the function
returns.  So we're outside of the "most of the time" case, since
you're not allowed to try to notice any change.  Pragmatism takes
over, you want the type that catches as many possible types as
possible while staying reasonable (volatile is never reasonable), and
that's const void *.  As simple as that.

As for releasing resources through const pointers, that happens all
the time as soon as your const use is tight, and if you think forcing
the systematic addition of a (void *) cast is going to make your code
more readable, well, you need more experience in maintaining other

delete in C++ allows const pointers. Think about it.

  OG.
--

From: J.A.
Date: Friday, January 18, 2008 - 3:29 pm

That's what __attribute__ ((pure)) is for, but if none of the
functions is pure, the compiler can not be sure about side effects
and can not reorder things. Don't forget that functions can do
anything apart from mangling with their arguments.

And allocator/deallocator functions never can be pure, they must
change global data, the pool of free blocks.

--
J.A. Magallon <jamagallon()ono!com>     \               Software is like sex:
                                         \         It's better when it's free
Mandriva Linux release 2008.1 (Cooker) for i586
Linux 2.6.23-jam05 (gcc 4.2.2 20071128 (4.2.2-2mdv2008.1)) SMP PREEMPT
--

From: Krzysztof Halasa
Date: Friday, January 18, 2008 - 4:44 pm

Though it seems it could legally transform:

void kfree(const int *x);

{
int v, *ptr = malloc(sizeof(int));
*ptr = 51;
v = *ptr;
kfree(ptr);
printf("%d", v);

into:

{
int v, *ptr = malloc(sizeof(int));
*ptr = 51;
kfree(ptr);
v = *ptr;
printf("%d", v);
}

if it knows that malloc generates unaliased pointers, which seems
reasonable in general.
-- 
Krzysztof Halasa
--

From: Andy Lutomirski
Date: Friday, January 18, 2008 - 6:54 am

I'd say this implies the exact opposite.  It almost sounds like the 
compiler is free to change:

void foo(const int *x);
foo(x);
printf("%d", x);

to:

void foo(const int *x);
printf("%d", x);
foo(x);

especially if it can prove that the pointer to x doesn't otherwise 
escape or that foo doesn't call anything that could see the pointer (and 
given that gcc has special magical markings for malloc, one way this 
could be "proven" is to have x be some freshly malloced object.

If foo is kfree, then the above transformation is clearly invalid.

(Note that this isn't just a problem for optimizers -- a programmer 
might expect that passing a pointer to a function that takes a const 
pointer argument does not, in and of itself, change the pointed-to 
value.  Given that const certainly does not mean that no one else 
changes the object, I'm not sure what else it could mean.  kfree does 
not have either property, so I'm don't think it makes sense for it to 
take a const argument.)


--

From: Vadim Lobanov
Date: Friday, January 18, 2008 - 12:14 pm

That's absolutely not true.

Let's unravel the code, by fixing usage of 'x' (which seems to vary at will 
between value and pointer in the above example), and by replacing printf with 
another opaque function. Our decls:
	void foo(const int *ptr);
	void bar(int val);
You're saying that this:
	foo(&x);
	bar(x);
can be reordered into this:
	bar(x);
	foo(&x);

No way. First, the way that const is currently defined, the compiler cannot 
assume that the value of x did not change while foo was executing. So, it 
will not only be forced to leave the two functions in that order, it will 
even reload the value of x before passing it into bar. Go figure.

Second, even if const did have stronger semantics that forbade the value of x 
from being modified during execution of foo, the compiler still could not 
reorder the two function calls, before it cannot assume that the two 
functions (in their internal implementations) do not touch some other, 
unknown to this code, global variable.

-- Vadim Lobanov
--

From: Vadim Lobanov
Date: Friday, January 18, 2008 - 12:55 pm

Oh, absolutely. The problem, however, is that very very few people actually 
use these function attributes in their code. Heck, we don't even use the 
standard restrict keyword, much less gcc-specific function annotations.

I think that one part of the problem is because gcc seems to have had 
attribute diarrhea -- I know of noone who can recite more than 10% of the 
available attributes without glancing at the documentation. The other part of 
the problem is that gcc does no sanity checks on the provided attributes. For 
example, the code below compiles perfectly fine without a peep, even 
with -Wall and -Wextra.

int global;

void foobar(const int *x) __attribute__((const));

void foobar(const int *x)
{
        if (*x)
                *(int *)x = 7;
        global = 11;
}

*grumble, grumble* :-)

-- Vadim Lobanov
--

From: Vadim Lobanov
Date: Friday, January 18, 2008 - 1:30 am

The restrict keyword controls aliasing, to be exact. And I'm skeptical that 

I must ask what relationship you think the const keyword has to compiler 
optimizations. I know of none, and I've yet to see that keyword cause any 
difference in the resulting assembly. It forces you to make your code clean 
and well-structured, but that's about it.

Of course, it would be an interesting experiment to potentially redefine the 
const keyword to have stronger semantics, such as having the compiler assume 
that a function taking a const pointer argument will not modify the memory 
the pointer points to, and thus saving itself a memory load in the caller 
after the function executes, as long as the data is not global. I imagine 
that this would lead to some simple and measurable optimizations, all the 
while (this is where I get into hand-waving territory) breaking a minimum 
amount of code in current existence.

But that is emphatically not how C is currently defined, and you're basically 
inventing an entirely new language... C2009 perhaps? :-)

-- Vadim Lobanov
--

From: Giacomo A. Catenazzi
Date: Friday, January 18, 2008 - 4:47 am

"restrict" exists for this reason. const is only about lvalue.

You should draw a line, not to make C more complex!

Changing the name of variables in your example:

extern print_int(const int *);

int main(int argc, char **argv)
{
   extern int errno;

   errno = 0;
   print_int(&i);
   return errno;
}

print_int() doesn't know that errno is also the argument.
and this compilation unit doesn't know that print_int() will
modify errno.

Ok, I changed int to extern int, but you see the point?
Do you want complex rules about const, depending on
context (extern, volatile,...) ?

ciao
	cate
--

From: Jakob Oestergaard
Date: Friday, January 18, 2008 - 7:39 am

On Fri, Jan 18, 2008 at 12:47:01PM +0100, Giacomo A. Catenazzi wrote:

You think that I try to put more meaning into const than I do - but I don't.

Please read what I wrote, not what you want to think I wrote.

I agree that if I said what you seem to imply I said, then I would have been
wrong. But I didn't so I'm not ;)


-- 

 / jakob

--

From: Vadim Lobanov
Date: Friday, January 18, 2008 - 12:06 pm

Except that changing int to extern int makes all the difference in the world: 
the variable went from being local to being global. The way const is 
currently defined, however, the compiler cannot take advantage of the fact 

Sometimes complexity is worth it.

-- Vadim Lobanov
--

From: Björn
Date: Friday, January 18, 2008 - 6:31 am

Not at all.

#include <stdio.h>
#include <stdlib.h>

char *lookup[5];

const char *get()
{
	*(*lookup = malloc(1)) = '1';
	return *lookup;
}

void set(const char *d, char val)
{
	for (int i = 0; i < 5; ++i)
		if (lookup[i] == d)
			*(lookup[i]) = val;
}

int main()
{
	const char *p = get();

	printf("%c\n", *p);
	set(p, '2');
	printf("%c\n", *p);

	return 0;
}

Do you see anything that casts the const away? No? Me neither. Still,
the memory that p points to was changed, because there was another

The only thing that const can tell you is that you should not modify the
value _yourself_, using that pointer _directly_. It's somewhat like a
soft "half" protected/private specifier. You may read this value, but if
you want to write to it, please use the setter function I provide for
you. Because that setter function might do some special stuff, like
counting how often that value was written.

And accepting a pointer to a const as an argument does _only_ say: It's
ok to call this function if you only received a pointer to a const, the
function does the Right Thing for such pointers. It does not guarantee
at all, that the function won't change the memory the pointer is
pointing to. Take a set of functions that manage memory for foo objects:

const struct foo *get(someIdentifier);
struct foo *makeWritable(const struct foo *);
void free(const struct foo *);

get() returns a pointer to a foo object, and it might return a pointer
to a _shared_ instance. Obviously it should make the pointer const, the
caller should not modify the shared instance.

makeWritable() accepts a pointer to a const foo, because you generally
want to pass it such a pointer to get a non-const one instead. The
function might just use ref-counting and see if it needs to create a
copy returning a pointer to points to a different location in memory or
if it can just return its _internal_ _non-const_ pointer. No casts!

free() also accepts a pointer to a const foo, because you obviously ...
Previous thread: What's in linux1394-2.6.git? by Stefan Richter on Thursday, January 17, 2008 - 2:07 pm. (3 messages)

Next thread: [PATCH 3/20] UML - SMP locking commentary by Jeff Dike on Thursday, January 17, 2008 - 2:40 pm. (1 message)