tags:

views:

517

answers:

11

I have a dictionary that goes like this:

typedef struct dictNode {
    int key;
    char *value;
    struct dictNode *next;
} Dict;

And a get() function that goes like this:

char *get(const Dict *dict, int key) {
    if(!dict) return NULL;

    Dict *currPtr = dict;

    while(currPtr) {
     if(currPtr->key == key) {
      return currPtr->value;
     }

     currPtr = currPtr->next;
    }
}

Compiling this code produces the following error:
dict.c:85: warning: initialization discards qualifiers from pointer target type

This warning refers to the line:

Dict *currPtr = dict;

If I add a "const" before that line like this:

const Dict *currPtr = dict;

The warning goes away...

1) First thing I don't understand is this: I added "const" to the dict argument in get() so that the compiler warns me if I try to change the address dict is pointing to, otherwise I won't be able to access the dictionary in the main program cause I lost the address I was pointing too. Now, I'm creating a new pointer, currPtr, that points to the same place as dict. This way, I use this pointer instead, to traverse the dictionary and keep dict pointer intact. Why do I also need to have const for currPtr?

2) Second thing I don't understand is this: The line currPtr = currPtr->next; is changing the currPtr pointer, so, why doesn't the compile warn me about that if I added a const to currPtr?

Then I have a del() function that goes like this:

Dict *del(const Dict *dict, int key) {
    if(!dict) return NULL;

    Dict *currPtr = dict;
    Dict *prevPtr = dict;

    while(currPtr) {
     if(currPtr->key == key) {
      prevPtr->next = currPtr->next;
      free(currPtr);
     }

     prevPtr = currPtr;
     currPtr = currPtr->next;
    }

    return dict;
}

Please note that I'm aware that this delete function code is not complete, it does not correctly work if I want to delete the first element for instance. It doesn't matter, I'll finish later, it suffices to demonstrate my problem...

3) So, in the get() function I needed to add const to currPtr but in this del() function, I don't need to add const to currPtr nor prevPtr? In both functions I am changing the currPtr pointer and in the case of the del() function I'm changing the prevPtr pointer too. Why does the get() function require me to add a const before currPtr and the del() function does not require me to add a const before currPtr and prevPtr?

I can basically resume this whole post to: When and where exactly in my get() and del() functions should I use const and why, and, when and where I shouldn't?

+1  A: 

A const is sort of programmer documentation. It tells yourself that you will not attempt to modify anything that was passed in const qualified.

Your get function should take a const Dict * whereas your del function should not. The currPtr is a walker -- it does not change the logical state of the Dict object in your get so it should be const qualified as well. However, in your del it changes the structure hence -- no const here.

dirkgently
+7  A: 

"I added "const" to the dict argument in get() so that the compiler warns me if I try to change the address dict is pointing to"

In that case you meant Dict *const dict, not const Dict *dict. You've declared the struct as const, not the pointer to it.

"Why do I also need to have const for currPtr"

Because otherwise you could use currPtr to modify the dictionary struct itself, which is supposed to be const in this function.

"The line currPtr = currPtr->next; is changing the currPtr pointer, so, why doesn't the compile warn me about that if I added a const to currPtr"

Same reason: it's not an error if the const is in the place you put it. It would be if the const were in the other place for currPtr.

otherwise I won't be able to access the dictionary in the main program cause I lost the address I was pointing too

No - when the main program calls get, it passes a pointer value into the routine, and this becomes the value of dict in the get function. But get has its own pointer variable, separate from the one in the main program. Even if they just so happen to both be called "dict", changing one pointer won't change the other.

Both variables point to the same struct, so if you use the variable to modify any of the fields in the struct, then of course the results will affect both bits of code. Usually, a function called "get" would be a read-only operation, so you're right to make the parameter const Dict *. But I think the reason this is right, is slightly different from what you think. It's to protect the contents of the dictionary from change, not to protect your record of the address of it.

However, const-safety is slightly awkward for linked lists like this one. Even when you have a const Dict, its "next" field still doesn't point to const. So although you can't modify the first node, the const-system won't protect you from modifying other nodes in the list. C++ addresses this with function overloading, so you can have a "getnext" function which returns a const output if the input is const, but a non-const output if the input is non-const. You could achieve a similar thing in C with a "getnext" and "getnext_c", the latter having a const parameter and return. But most people don't bother, including standard library functions like strchr, which in C takes const char* but returns non-const char*. strchr can therefore be accidentally used in C to convert a const string into a non-const one, without any warning. Or deliberately, in which case it's a bit like laundering money through a "legit" business ;-)

the del() function does not require me to add a const before currPtr and prevPtr?

I confess I'm stumped on that. I would expect the same warning as in "get" - are you sure it isn't there? But the parameter to del shouldn't be const anyway, because you're potentially modifying the fields.

Steve Jessop
+3  A: 

const Dict *dict means this is a pointer to constant Dict values

you could modify the pointer, but not the values;

Indeera
+2  A: 

I think your main confusion comes from misunderstanding the meaning of

const char* p;

This defines a pointer to a constant character. The address in p can change, but the value pointer cannot. What is forbidden by the compiler is to change *p, not p.

David Cournapeau
+1  A: 

Ad 1 & 2: The const in the prototype applies to the Dict, that means

char *get(const Dict *dict, int key)

reads as ... function with a (non-const) argument of type pointer to constant Dict ..., which means you can change the dict, but not eg. dict->next.

If you want to get the effect you probably intended, declare it like this

char *get(Dict * const dict, int key)

The rule is: The const applies to the word that is to the left of it, except when it is first in the type name, and then it applies to the next word.

Also, changing dict doesn't change the pointer in the main program, since the pointer is passed by value.

When assigning to the currDict pointer, the compiler checks that the types are compatible, which, in this case, means that you can only add top-level const qualifiers, not remove as you do.

Where should you use const:

In the get() function, you should use const on all Dicts (as you do), as you probably don't want to change the dictionary there.

In the del(), function, I wouldn't use any consts, since the data are to be changed there. A good signature for it would be:

char *get(Dict ** dict, int key)

so you would be able to change the calling function's pointer to delete the first element.

jpalecek
+1  A: 

Keep in mind that const may appear on either or both sides of * in a pointer declaration, and that's semantically significant...

const char * foo = bar;

is not the same as

char * const foo = bar;

The first says the content is const, and the second says the pointer is const.

For maximal constness there's:

const char * const foo = bar;
dwc
Isn't it the other way around" "char * const foo" is a constant pointer, and "const char * foo" is a pointer to constant data.
mipadi
I knew that! It was a typo, really! ;-) (edited)
dwc
+2  A: 

It may help to read the pointer declaration backwards:

const Dict *dict

means

'dict' is a pointer to a Dict that is constant.

You can move the address it's pointing to, but the struct it is pointing to is treated as constant.

veefu
+2  A: 

The compiler is keeping track of what is and what is not const. Getting them right can be tricky at first.

For your first point, you're passing in const Dict *dict, which means you're promising that you aren't going to modify anything through dict. Then you assign it to Dict * currptr. Since currptr isn't const, you could change things through *currptr. The compiler has to warn you at some point that you're trying to modify something even though it's const, and the right place is when you're assigning a const pointer to a non-const pointer.

For your second point, you're missing exactly what's const. If you have const Dict * currptr, you're describing currptr as a pointer to const Dict, meaning you can change currptr but not *currptr. You can make currptr const with Dict * const currptr, or make it a const pointer to a const value with const Dick * const currptr.

I don't actually understand your third point. You should get a compiler warning from del() as you've posted it, since you're assigning a pointer to const value to pointer to value. You need to have prevptr non-const, since you're using it to modify a value. The function signature should be Dict *del(Dict *dict, int key), since as it is you're promising not to modify anything dict is pointing to, and doing it anyway.

David Thornley
+13  A: 

Without a pointer, you would have

const Dict currPtr

which is a constant Dict. Now, if you make it a pointer you have

const Dict *currPtr

which is a pointer to a constant Dict. That does not mean that the pointer is constant. But it does mean that the Dict pointed to is treated as constant

currPtr->key = 10; // error, dict is treated as constant.

But the pointer is not

currPtr = otherPtr; // possible: the pointer is not constant

You would get an error for the second case if you make the pointer constant. Keeping the pointed dict constant too, this would look like this

const Dict * const currPtr = init;

Now you can't set currPtr to point to something different, because the pointer is now constant, not just what the pointer points to is treated so. Some people like the look if the const is always right to the stuff that it makes const. This would look like this

Dict const * const currPtr = init;

which is the same as the previous snippet. If you then read it from right to left, it tells you what it is "const pointer to a const Dict". If you have a type, it doesn't matter how you order the specifiers

int const a = 10;
const int b = 10;

Both are constant integers. That is why we could put const right of the Dict type specifier.

Now, if you have a pointer, you can always pretend you point to a constant object, even though the object wasn't declared const. But you can't pretend to work with a non-const object if what you point to is a const object:

int const *p = NULL;
// doesn't work without a cast. Will at least provoke a warning
int *pn = p;

int *p = NULL;
// always works: pretending to point to something const doesn't harm.
int const *pc = p;

Note that if you make the pointer itself const, the rules are different to that. They are analogous to const applied to other types:

int const i = 0;
int j = i; // works. we only read the value of i. its const doesn't matter. 

int * const p = NULL;
int * q = p; // works: we only read the value of p (a null pointer).

After copying the value into a new variable (whether pointer or not), the new variable is not connected in any way to the other variable, because the value read has no associativity to how the value was created in the first place. The const of the other variable doesn't matter.

Johannes Schaub - litb
A: 

I get most of the things you people are saying, what I don't get now, is this...

Consider the following list() function:

void list(Dict *dict) {
    if(!dict) return;

    while(dict) {
     printf("KEY: %d\n", dict->key);
     printf("VALUE: %s\n", dict->value);

     dict = dict->next;
    }
}

(it's simpler to use this one instead of the get() one)

In my main I have two calls to this function, like:

list(dict);
list(dict);

Of course, dict has some entries inserted...

To my understanding, only the first list() should output something because the dict pointer is being changed in the list() function and will eventually point to NULL (otherwise we would be in an infinite while loop in the list() call). What's happening is that the second list() call is also outputting the same dictionary, when I think it shouldn't output anything because dict should be NULL, no?

Nazgulled
This is a whole different question, but... the "dict" parameter is only changed within the scope of list(). C parameters are value parameters.
dwc
C is a "pass by value" language. When you call list(), a new stack frame is created. Dict* dict is copied onto that frame, so you can only modify it locally; if you were to say dict = null, you're not writing to the same memory location that list(dict) reads from.
ojrac
I see... thanks :)
Nazgulled
A: 

The "Dummies Guide to const":

Use it whenever you can.

T.E.D.