mikeash.com: just this guy, you know?

Posted at 2009-06-26 16:11 | RSS feed (Full text feed) | Blog Index
Next article: Friday Q&A 2009-07-03: Type Specifiers in C, Part 2
Previous article: Friday Q&A 2009-06-19: Mac OS X Process Memory Statistics
Tags: c fridayqna
Friday Q&A 2009-06-26: Type Qualifiers in C, Part 1
by Mike Ash  

Welcome back to another warm and fuzzy edition of Friday Q&A. This week I'm going to discuss the use of type qualifiers in C, a subject suggested by Nate Vander Wilt.

What They Are
The first thing is to talk about what type qualifiers actually are. In short, they're a keyword which can be used to modify the properties of an arbitrary type. The most common one is const. Type qualifiers should not be confused with keywords which modify the storage of a particular variable. Those are called storage specifiers.

Let me illustrate with an example:

    static const int *x;

Here, static is a storage specifier. It modifies the variable x to change how x is actually stored. The const keyword is a type qualifier, which modifies the int type.

By way of illustration, it makes perfect sense to use const in a typedef, like so:

    typedef const int MyConstInt;
But it makes no sense to use static in this way, because static is not part of the type:
    typedef static int MyStaticInt; // will not compile, does not make sense!
The C99 language contains three type qualifiers:

This week I will discuss const and restrict, and I'll finish up next week with a discussion of volatile.

The const keyword
The const qualifier is far and away the most common and the most useful of the three. Its meaning is very easy to understand: when applied to a type, const makes that type become read-only.

There are several places where const can be useful:

  1. On a function pointer parameter. A const function pointer parameter means that the function won't modify the value that the pointer points to. For example, look at the standard strchr function. The first parameter is declared of type const char *, because this function only reads the string, and doesn't modify it. This is a useful thing to do with your own code as well when taking pointer parameters that won't be modified.
  2. On a function pointer return value. Used here, const indicates that the data being returned is read-only and the caller is not allowed to modify it. For example, the -[NSString UTF8String] method in Cocoa returns a const char *. This means you can use the data but you're not allowed to write to it. The data may be a pointer to some kind of internal storage or cache, and the const is used to enforce access to make this useful.
  3. On a local or global pointer variable. When used here, const indicates that this pointer can't be used to modify its contents. This can be useful to preserve correctness when you know that your use is read-only, but is most often useful to shut up the compiler when accessing const return values from functions that return const pointers as in #2.
  4. On a local or global non-pointer variable. This is handy to declare a compile-time constant. For example, const int kMeaning = 42; declares a constant. The const here will help prevent you from changing this value by accident, and may also allow the compiler to do some optimizations that otherwise would not be possible.
Note that this is not meant to be an exhaustive list, and there are probably other interesting places to use it as well.

Since this is a heavily Mac-centric blog, I also want to briefly discuss const as it applies to Objective-C. In particular, you should never declare an Objective-C object type as const. For example, this is not a good way to enforce the immutability of an NSString:

const NSString *immutableNSStringPointer;
What const means here is that you can't use immutableNSStringPointer to modify the memory at that location. But the immutability of an NSString is part of the API contract only. Nothing says that the memory of an NSString can't be modified, only that the semantic contents of the NSString can't be modified. For example, NSString might have some internal caches that get updated, but which don't affect the conceptual contents of the string. Changing those caches would violate the const requirement, but not the immutability of the NSString.

More concretely, if you declare a variable like this and then try to use it anywhere, you'll get a huge number of useless warnings about violating the constness of your variable.

What if you want a constant NSString pointer? Not a pointer to a constant NSString, but a constant pointer, one which can't be changed. The NSString equivalent of the const int kMeaning = 42; example from above. This can easily be done, you just have to apply the const to the variable directly, by altering its position:

    NSString * const kConstantStringPointer = @"hello, world";
The restrict Keyword
The restrict keyword is new in C99. (The other two date from C89.) This one is purely for the purposes of optimization. That fact, combined with the fact that it's kind of hard to understand, means that this keyword is extremely rare to see and even more rare to actually use.

So what does restrict mean, exactly? It's actually pretty simple: when a pointer is declared with restrict, it tells the compiler that this is the only pointer which will be accessing a particular chunk of memory in that scope.

But what does that mean? Consider the following code:

    char *src, *dst;
    ...
    for(int i = 0; i < len; i++)
        dst[i] = src[i];
When compiling this code, the compiler will need to generate an individual memory load followed by an individual memory store for each iteration of this loop. There are techniques to make this loop run significantly faster by loading and storing larger chunks of memory at once, but the compiler can't use them. Imagine what would happen if dst pointed to src + 1. Each time the assignment is made, it also alters the value that will be used for the next iteration of the loop. Unless the compiler can rule out this possibility (which is extremely difficult to do automatically) it's forced to generate very slow code.

This is where the restrict keyword comes in. By declaring src and dst as restrict, you tell the compiler that you are personally guaranteeing that they won't point into the same block of memory, and thus that the compiler should feel free to generate nice, fast code for this loop.

It's unlikely that you'll have occasion to use restrict in your own code. However you may encounter it elsewhere. For example, here is the prototype for the memcpy function:

    void *memcpy(void *restrict s1, const void *restrict s2, size_t n);
When used on a function argument like this, it means that you must not provide overlapping pointers to this function. That's part of memcpy's API contract: it only works on blocks which don't overlap. (The memmove function is provided for blocks which potentially do overlap.) Previously this was only expressed in the documentation, but using restrict it can actually be expressed right in the code.

Wrapping Up
That brings us to the end of Part 1. Now you know what const and restrict mean and how to use them. The const can be very useful and every C programmer should know how to use it. The restrict keyword is mostly useless unless you're writing certain kinds of highly optimized code, but it's still good to know the basics.

Next week I'll discuss the volatile keyword. It sits in an interesting middle ground in between const and restrict: not nearly as useful or common as the former, but much more widely used and marginally more useful than the latter. It's also very frequently misunderstood and misused. For all the details on what it does, how to use it, and (most importantly) how not to use it, check back next week.

And as always, Friday Q&A runs on your ideas, so send them in! If you have a subject that you would like to see discussed here, post it in the comments or e-mail it to me directly.

Did you enjoy this article? I'm selling a whole book full of them. It's available for iBooks and Kindle, plus a direct download in PDF and ePub format. It's also available in paper for the old-fashioned. Click here for more information.

Comments:

Ahruman at 2009-06-28 07:23:17:
const int kMeaning = 42; does not declare a constant in C. It declares (and defines) a variable which is read-only. (In C++, a const variable is a true constant if its definition is visible.)

The distinction can be seen in code such as the following (in global scope):
const int kMeaning = 42;
char buffer[kMeaning];
const int kDerivedConstant = kMeaning + 1;

error: variable-size type declared outside of any function
error: initializer element is not constant

This is why enums are commonly used for integer constants which are not actually used as enumerants - they are actual constant expressions which can be used in calculations.

Ahruman at 2009-06-28 07:23:50:
Oh, and your comment form crashes if it's fed non-ASCII. :-)

mikeash at 2009-06-28 07:47:45:
I'd argue that it does give you a constant, in that it's a value which cannot be (legally) changed. What it does not do is give you a constant expression when you use the thing. This is peculiar, and annoying, and takes away from some of the utility of this approach, but I still think it's right to call the thing a "constant". This is, of course, hair-splitting.

As for non-ASCII comments, I spent a long time fighting Python and MySQL and finally decided that it was simply impossible to make them agree on anything character-set-wise. Someday when I redo the blog system in SQLite it will all work, but until them, well, I'm stuck with ASCII.

dz at 2009-07-03 22:25:16:
And there is no difference between

const int kMeaning = 42;

and

int const kMeaning = 42;

, right?

mikeash at 2009-07-03 22:56:03:
That's correct. Leading qualifiers, specifiers, and types can be mixed up however you like. static volatile const int whatever; is the same as int volatile static const whatever;.

sam at 2009-07-03 23:36:12:
In global scope what's the difference between cont int kMeaning = 42; and #define kMeaning 42 ?

mikeash at 2009-07-04 00:37:59:
The #define simply substitutes the literal string "42" anywhere you write "kMeaning". The other one defines an actual variable, one you're not allowed to modify. As for practical differences, Ahruman covered one big one: the const int is not actually considered to be a constant expression by the compiler, which influences where you can use it. Another one is that it is legal to take the address of the const int, but obviously not of the #define (&42 is not a legal expression).

John Gallagher at 2009-07-05 09:00:00:
Mike

Thankyou so much for an incredibly useful article. I'm too tired to fully digest it right now, but I've been hunting for a proper detailed article on this for ages, being a complete Cocoa newbie and seeing const everywhere, and using it whilst not really understanding what I was doing.

One thing I don't see mentioned here is the extern keyword. Have you covered this elsewhere, or is there another Q and A to be had on the extern keyword and what this is?

John

mikeash at 2009-07-05 10:12:29:
I imagine there are many good sites which explain extern. Let me give you the quick version, though.

In essence, an extern variable declaration is to a regular one what a function prototype is to an actual function. It tells the compiler that this variable exists, but does not actually make it exist. It only makes sense on globals.

For example, both of these declare the existence of a symbol, but do not cause that symbol to be created:

extern int globalVariable;
void function(void);


And then these actually create the symbols above:

int globalVariable;
void function(void) {}


And just like functions and function prototypes, you should put the extern version in the header, and the non-extern version in the implementation.

slf at 2009-07-12 11:04:56:
I'm surprised go didn't talk about the 'double const' ie: char const* const

const char* and char const* are pretty much the same thing: they allow you a pointer to a collection of characters that will change. Doing a char const* const means that not only is the data constant, but the pointer is constant as well.

In summary, just because the data can't change, doesn't mean the pointer can't. To make that const, you need a second one. Unfortunately I am not as good at explaining things a you are, so I would be interested in reading your view. Thanks for the great blog!

mikeash at 2009-07-12 11:29:56:
const char * and char const * are not "pretty much the same thing". They are identical. They produce an identical type on the variable when compiled. The C language does not care about the ordering of type qualifiers, storage specifiers, and types when they're intermixed like this.

As for pointer constness versus pointed-to constness, I covered that in a more specific fashion in the section that talks about how to make constant NSStrings.

John Gallagher at 2009-07-12 19:16:45:
Mike

Thanks a lot for your explanation. Very helpful. Loving the Q and A series - as a newbie Cocoa guy, it's really useful.

John

johne at 2009-09-17 12:35:19:

const NSString *immutableNSStringPointer;

What const means here is that you can't use immutableNSStringPointer to modify the memory at that location.


This isn't quite correct. What the statement means is "a pointer to a const qualified NSString". A const qualified type is unchanging and immutable. In fact, the compiler is free to place const qualified data in read-only sections, such as MMU enforced read-only RAM, or even ROM. It's not so much that it means that you can't use immutableNSStringPointer to modify the memory at that location, it's that const changes the properties of the memory at that location so that modifying it is non-sensical.

This might seem pedantic, but it does have some important ramifications. For example, the optimizer will often take advantage of the fact that const qualified data can not change. This can lead to subtle, hard to debug bugs if the const qualified data does mutate.


Nothing says that the memory of an NSString can't be modified, only that the semantic contents of the NSString can't be modified.


I don't know what "semantic contents" means in this context, but the first part of the sentence is definitely wrong for immutableNSStringPointer. In fact, the standard explicitly says that modifying "the memory of an NSString", that is to say the memory pointed to by immutableNSStringPointer, will result in undefined behavior.


More concretely, if you declare a variable like this and then try to use it anywhere, you'll get a huge number of useless warnings about violating the constness of your variable.


The warnings aren't useless, they're letting you know that something is wrong. In this case it is probably because you're passing a const qualified variable as an argument where the declared argument type is not similarly const qualified. This can potentially cause problems in optimized code if the function or method modifies the pointed to memory, for example. These warnings are almost always worth investigating and correcting (if possible, sometimes the problem is out of your control, like CFString/NSString toll-free bridging).

mikeash at 2009-09-17 17:10:43:
johne: Well, you're confused about several things here....

Your statement that the compiler is free to put const data in read-only sections is completely true, but also completely irrelevant. The declaration const NSString *immutableNSStringPointer; does not create or otherwise involve const data. There is a huge difference between a const object and a pointer to a const type. Your stuff about read-only memory is completely unrelated to a pointer to a const type.

Likewise your stuff about optimizations. The comiler cannot and will not assume that the data pointed to by a pointer to a const type is unchanging. By way of illustration:

const int x = ...;
printf("%d\n", x);
const int *xp = &x;
SomeFunction(xp);
printf("%d\n", x);


Here the compiler is free to assume that the value of x could not be changed by the call to SomeFunction. Now consider this:

const int *xp = ...;
printf("%d\n", *xp);

SomeFunction(xp);
printf("%d\n", *xp);

Here the compiler cannot assume that *xp is the same value that it was before. It must re-read it. The const in this case does not have anything like the same semantics that it does in the first case.

As for "semantic contents", it means that an immutable NSString which contains any given unichar sequence, as queried through its public API, will always contain that same unichar sequence for the lifetime of the object. As for the rest of that paragraph, well, I wasn't talking about immutableNSStringPointer so it's irrelevant once again.

And as for "useless warnings", once again you misunderstand. They are useless because the purpose of declaring a pointer to be to a const type is to warn about mutations, but in this case the type of mutation they warn about is not the type of mutation you care about.

johne at 2009-09-17 22:14:41:
Mike,

From "The New C Standard" (re: const):

However, a translator is not required to perform any checks at translation or execution time to ensure that the object is not modified (via some pointer to it).

A translator may reduce optimization overhead by assuming const-qualified objects are never modified. This could result in values held in registers being reused, after the object held in storage had been modified.


I'm sorry to say that you're wrong. The compiler has no obligation to "re-read" the values like you claim. The statement "const int *xp;" has the exact same semantics as "const int x;", and the compiler is free to treat it as such. The fact that the values are access via a pointer changes nothing- the type for "const int *xp;" is 'pointer', and the type that it points to is 'const qualified int', which is exactly the same as 'const int x;'. The best way to demonstrate this is with code:

// Based on http://www.compileroptimizations.com/category/alias_optimization_const_qualifier.htm

const int *const_array;

void g(const int *x, const int *y) { printf("g: x: %d, y: %d\n", *x, *y); }

void f (int *p, int i) {
  int x=0, y=0;
  const int *q = &const_array[i];
  x = *q;
  *p = 9;
  g (&x, q);
  y = *q;
  g (&x, &y);
}

int main(int argc, char *argv[]) {
  int x1 = 123, i, int_array[] = {1,2,3};
  const_array = int_array;

  printf("x1: %4d, const_array: %d, %d, %d\n", x1, const_array[0], const_array[1], const_array[2]);
  f(&x1, 0);
  printf("x1: %4d, const_array: %d, %d, %d\n", x1, const_array[0], const_array[1], const_array[2]);
  f(&const_array[0], 1);
  printf("x1: %4d, const_array: %d, %d, %d\n", x1, const_array[0], const_array[1], const_array[2]);

  return(0);
}

outputs:

x1: 123, const_array: 1, 2, 3
g: x: 1, y: 1
g: x: 1, y: 1
x1: 9, const_array: 1, 2, 3
g: x: 2, y: 2
g: x: 2, y: 2
x1: 9, const_array: 9, 2, 3

mikeash at 2009-09-17 23:47:23:
Once again you confuse const objects with const types. The passage you quote is talking about const objects. It is disallowed to modify a const object at runtime. A pointer to a const type is completely unrelated to const objects, though, except for the fact that if you take an address of a const object, the type of the result is a pointer to a const type.

Right from your quote, emphasis added: "...assuming const-qualified objects are never modified." This means data qualified with const. It does not mean non-const objects pointed to by a const pointer. Your passage does not support this idea at all.

To illustrate the difference, consider this code snippet:

int x;
const int *y = &x;


Here you have a const object but no const objects anywhere in sight. If these are the only variables you declare in your program, then your program contains no const objects.

Now, consider this:

const int x;
int *y = (int *)&x;


Now you have a const object, but no const pointer. This is perfectly legal as long as you don't use the pointer to modify the const object.

In short, and pardon the repetition but it seems that you need it: anything that talks about const objects is completely irrelevant when discussing the sementics of const pointers.

In particular, regarding re-reading through a const pointer when the underlying value is modified, I quote the C standard, section 6.7.3, paragraph 3:

The properties associated with qualified types are meaningful only for expressions that are lvalues.


In other words, reading from a const value is just like reading from a regular value. As always, the "as if" rule is king: the compiler does not have to read a const value, but only if the behavior is guaranteed to be "as if" it had. If the object where the value is being read from is not in fact a const object, and it could have been modified, then the compiler must re-read it. In other words, this code must always print "12":

int x = 1;
const int *y = &x;
printf("%d", *y);
x = 2;
printf("%d", *y);


If you still think I'm wrong, feel free to find the portion of the standard which says so. There's a draft of C99 online here:

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1336.pdf

And my understanding is that there are no substantial differences between that and the final one.

As for your code example, I'm afraid I don't see the point of it. Its output is exactly what I would expect. Perhaps I misunderstood the purpose of it. Could you explain exactly how this code sample illustrates your point, as far as what output it should be giving if I were correct, and how your position causes it to print the output that it does?

mikeash at 2009-09-17 23:48:20:
And of course I screw up right in a critical portion. Where I wrote this:

"Here you have a const object but no const objects anywhere in sight."

This should of course say:

"Here you have a const pointer but no const objects anywhere in sight."

I hope this does not impede understanding of my larger point.


Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Name:
Web site:
Comment:
Formatting: <i> <b> <blockquote> <code>. URLs are automatically hyperlinked.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.