Dilemma of const-ifying a function parameter

Discussion in 'C Programming' started by hzmonte, Aug 27, 2006.

  1. hzmonte

    hzmonte Guest

    Correct me if I am wrong, declaring formal parameters of functions as
    const, if they should not be/is not changed, has 2 benefits;
    1. It tells the program that calls this function that the parameter
    will not be changed - so don't worry.
    2. It tells the implementor and the maintainer of this function that
    the parameter should not be changed inside the function. And it is for
    this reason that some people advocate that it is a good idea to
    const-ify even a non-pointer parameter:
    void my_func(const int i) { ... }
    because if the original programmer or a subsequent maintainer
    accidentally change the value of the parameter, the compiler can catch
    A third benefit may be that the compiler can optimize the program if it
    knows for sure that the parameter is not changed inside the function. I
    am not sure about this though.
    Now, I would like to benefit from the const-ification of the function
    parameter. Let's say I have this code:

    typedef struct s_fifoItem t_fifoItem;
    struct s_fifoItem {
    t_fifoItem *next;
    t_fifoItem *prev;
    void *elem;
    typedef t_fifoItem t_fifoQueue;

    void enqueue(t_fifoQueue *q, void *elem)
    t_fifoItem *i, *last;

    i = malloc(sizeof(t_fifoItem));
    i->elem = elem;
    last = q->prev;
    last->next = i;
    i->next = q;
    q->prev = i;

    elem appears to be a good candidate for const-ification. Intutitively
    the program calling enqueue() normally does not expect that the item to
    be enqueued is to be changed. And the implementor of enqueue() would
    not want to change elem either; and actually elem is not changed in the
    above implementation. But if I const-ify elem, gcc would complain (in a
    warning) that the assignment
    i->elem = elem;
    would discard the const qualifier because the type of "i->elem" is
    "void *", not "const void *". One solution is to cast away the
    const-ness of elem in that assignment:
    i->elem = (void *)elem;
    Another solution is to change the declaration of elem in struct
    s_fifoItem to be:
    const void *elem;
    The latter solution is not practical because there are other places
    that will change *elem.
    However, the former solution of casting away the const-ness is not very
    appealing. It is kind of cheating.
    So, how do people resolve this? And to a deeper level, gcc seems to be
    caught bewteen 2 conflicting goals: ensuring type correctness (a double
    cannot be assigned to an int, a const void * cannot be assigned to a
    void *, etc) and ensuring the unmodifiability of a variable. From the
    point of view of language design, what are the issues involved and how
    do other programming languages deal with it?

    P.S. I know nothing about C++. But in its queue STL, there is a
    void push(const T &val)
    The const here means that the element is a const parameter. This
    confirms my belief that it is good programming practice to const-ify
    the element to be enqueued. But I wonder how this const-ification is
    implemented inside push(). It is possible that the parameter is made
    constant in the prototype from the client's perspective (to reap the
    first benefit I mentioned above) but the parameter is cast (void *)
    inside the function (so the second benefit is not reaped, and the STL
    coders (i.e. ones who code the STL) need to be careful).
    hzmonte, Aug 27, 2006
    1. Advertisements

  2. hzmonte

    Michael Mair Guest

    Here you are going wrong.
    There is a conceptual difference between
    void my_pfunc (const int *pI);
    void my_func (const int i);
    For the former, whatever pI points to must not be modified
    but pI may be modified. The equivalent of the latter is
    void my_pfunc2 (int * const pI);
    which does not allow to modify the _value_ of the parameter.
    As the change of the parameter value has no bearing whatsoever
    on the argument being passed, there is indeed only the local
    "benefit" of not being able to change the parameter.
    This IMO is more a question of style than anything else.
    If you want to do that consequently and have benefit 1 and 2
    on top of it, you arrive at
    const Type
    <some number of "* const" and plain "*" in between>
    i.e. you will have
    const int * const pI
    for the above and
    const int ** const ppI
    const int * const * const ppI
    to be safe on either the level of the values at the bottom
    and level of the pointer value or on all levels.

    You forgot to check the return value of malloc().
    Undefined behaviour from here.
    Note that
    i = malloc(sizeof *i);
    is safer in that you have not to adapt the line if the
    type of i changes (e.g. copy&paste+adaption).
    Make [*]
    const void *elem
    if you want to never ever modify *elem via i->elem
    and never to be able to modify *(i->elem) when you get
    the address from i->elem instead of another copy of the
    Then "const"ing *elem via "const void *elem" makes sense.
    This is only the case if you want to store the address for
    comparisons of either address or value pointed to.
    There is a difference between "the pointer parameter will not be
    used in the function to modify what the pointer points to" and
    "the address passed via the pointer parameter and its copies made
    during the function's lifetime are not used to modify what the
    pointer points to". You essentially promise the latter when saying
    "const void *elem".
    Once again: const-ifying elem means "void * const elem" and that
    the parameter will not change its value. If you want to be able
    to say "the value elem points to is not changed but the value
    i->elem points to may be changed" then you have also to pass the
    size of *elem and create a copy of *elem:
    i->elem = malloc(size);
    if (i->elem == NULL) {
    /* Your sensible action here */
    memcpy(i->elem, elem, size);
    gcc just does the thing the language is promising.

    Note that for C++ templates, the type of the passed val is known,
    thus a local copy can be made, just as I suggested above.
    If the queue stored a "const T *" or "const T&" copy of val, then
    you have once again a pointer or reference to dVal=(whatever
    dereferencing val gives you) that cannot be used to change dVal.
    If you change whatever is stored in the queue, then you have no
    changes whatsoever to dVal.

    This is just the same as giving &dElem to enqueue() -- the
    prototype of enqueue promises you that dElem will never ever
    be changed by the address given to enqueue() or one of its
    copies made during the run-time of enqueue(). At least if
    enqueue() is honouring the promise of "const void * elem".

    Michael Mair, Aug 27, 2006
    1. Advertisements

  3. hzmonte

    Malcolm Guest

    What you are describing is const poisoning. Very few variables, by
    definition, are truly constant through the life of the program. However they
    may be constant for a defined period of time.
    const was added to C by a committee. Committees are good at taking certain
    types of decisions, but language design isn't one of them. A good language
    is more like a Renasissance masterpiece than a ladies' knitting circle tea
    rota. It doesn't express the constness of data very well.
    The question is, what to do about it? There is no good answer. If you reject
    const algoether you risk making code uncallable by those folish enough to
    use it, if you use it you get these types of problems.
    Malcolm, Aug 27, 2006
  4. hzmonte

    Bill Pursell Guest

    FWIW, I don't ever see the point of doing this. If you want
    that type of behavior, it's much more reasonable to do:
    void my_func(int i) { const int i_copy = i; ...}. If the problem
    is something like:
    void my_func(len I)
    for ( ; len > 0 ; len--)
    /* oops, future maintainer thinks len is still valid */

    then making len const just forces you to add a loop
    variable and the new code becomes the above
    suggestion, with the const transposed. Usually, when
    I see foo(const int arg) I take it as a clue that the
    author is a newbie and it raises warning flags.
    The restrict keyword is probably more beneficial for optimization,
    but I believe const has an effect as well. (I have no empirical
    evidence to support this claim.)

    I would go with the former, but a slight modification
    of the second solution is to provide an accessor function
    which casts away the const from the structure. eg
    void *ret;
    ret = (void *)(i->elem)
    This has essentially exactly the same problems you
    describe, but it localizes the cast.
    I've been wondering about this for some time, and I'm starting
    to lean towards using const less often than I'd like. For example,
    consider an implementation of strchr:
    error_t my_strchr(const char *string, char target, const char
    Where instead of returning the index, you put a pointer to the
    first occurence of target in *location. If you don't constify
    location, you'll need to cast its assignment in my_strchr. If you
    do constify it, then the caller must always pass consts: ie to
    replace 'a' with 'b', you'd like to do:

    while (my_strchr( string, 'a', &location)) {
    *location = 'b';

    but instead you'll need to cast away the const of location. Again,
    I think the solution here is to cast the assignment within my_strchr.
    It's aesthically unappealing, but IMO it's the correct thing to do.
    Bill Pursell, Aug 27, 2006
  5. No.

    #include <stdio.h>
    void foo(const int *a, int *b) {
    printf("*a = %d; *b = %d\n", *a, *b);
    printf("*a = %d; *b = %d\n", *a, *b);
    int main(void) {
    int c = 1;
    foo(&c, &c);
    return 0;

    There is no way compiler can tell whether the thing pointed by a const
    int* pointer will change or not. You'll had to use 'restrict'
    keyword for that:

    void foo(const int restrict *a, int restrict *b) { /* ... */

    However, still it's up to programmer not compiler to guarantee that
    a and b point to different things.
    You'll have to constify it in the structure also and it may or may not
    break your code depending on what you do with the pointer.
    Then, constifing the formal parameter would be pointless.
    And that's what you have to do.
    So you cannot constify the parameter. The function doesn't change
    what elem points to but it doesn't matter. It saves the pointer
    somewhere for other functions to be able to modify it so indirectly
    the function is modifying it.
    It's stupid and pointless. :)
    If any other function will modify what elem points to you cannot
    constify the argument. Compare this to that code:

    void inc(int *bar) { ++*bar; }
    void foo(const int *const_bar) { inc((int *)const_bar); }

    Your saying: OK, foo() doesn't modify *const_bar so we can constify it
    but at the same time inc() modifies it so we need to cast it to
    non-const pointer but in the end doesn't foo() end modifying
    I don't know STL but probably there is also a const T &pop() method
    which returns *const* reference or val is being *copied* and then
    pop() returns non-const reference to the copy. In your case, you want
    a void push(const T *val) and T *pop() functions which is wrong.
    Michal Nazarewicz, Aug 27, 2006
  6. hzmonte posted:

    I use the exact same rules for defining function arguments as const, as I
    use for defining any object as const.

    Yes, I would define "i" as const if it weren't to be changed.


    void *const elem;

    No no no! Leave the function argument as a "pointer to const".

    There's a difference between an actual object being const, e.g.:

    int const i;
    int *const p;

    , and a pointer to const:

    int const *p;

    If you change the function parameter to "pointer to NON-const", then
    there's the danger of the following:

    char const buf[5] = {0};

    Because the address of the array will eventually be held in a "void*"
    (which is a pointer to NON-const), there's the danger of it being altered
    at some point.

    First thing to tackle is this:

    Should "s_fifoItem" contain a "void*", or a "void const*". If the
    former, then this implies that the pointer will be used to alter the data
    at that address. If the latter, this implies that the data won't be
    altered. If you choose to leave it as "void*", then make your function take
    a "void*".

    No it doesn't -- it means that val is a reference to a const T.

    (A reference can't be const because it isn't an object -- the following
    won't compile: int &const i)
    Frederick Gotham, Aug 27, 2006
  7. hzmonte

    hzmonte Guest

    Here you are going wrong.
    Where did I go wrong? I did make a mistake by saying constifying elem;
    what I meant was constifying *elem. That is, the data pointed to by
    the pointer is not changed.
    I do want to modify *elem outside this (enqueue) function. That is why
    I said I have a dilemma.
    Yes, I want that (latter) promise.
    Again, I should have said "const-ifying *elem".
    It would be more precise to say that what I want is "the value elem
    points to is not changed within the function but the value i->elem
    points to may be changed outside the function".
    Your suggestion resolved the problem but requires additional memory
    that is not necessary had C provided a way to get around this dilemma.
    There is no real need to change *elem in the function and thus no need
    to malloc a copy of *elem - the need arises only because C does not
    satisfy both goals (type compatibility and the desirability to
    represent a parameter as "does not change" in a function) at the same
    hzmonte, Sep 18, 2006
  8. hzmonte

    hzmonte Guest

    It is not pointless. Constifying the formal parameter tells the
    calling function that "Hey, don't worry, I will not change *elem within
    me." The caller needs not worry about any side effect.
    It matters because constifying a parameter makes a promise that it is
    not changed inside the function. Conversely speaking, if the parameter
    is not changed inside the function, then it can be const-ified. If the
    parameter is not changed inside the function and it still cannot be
    const-ified, then what is the use of "const"?
    I am inclined to disagreeing to your "indirect modification" argument.
    I think "const" promises that the parameter is not modified inside the
    function. It is a local promise. My understanding is that it is never
    intended to take into consideration whether the parameter would be
    modified outside the function, probably as a result of some action
    inside the function.
    The difference between your example and my enqueue() example is that in
    foo(), you do modify the parameter - the semantics of foo is that the
    parameter is incremented, but in enqueue(), *elem is not modified - no
    one expect by purely putting a data to a queue would modify the data.
    hzmonte, Sep 18, 2006
  9. hzmonte

    hzmonte Guest

    I meant the latter - the data won't be altered.
    hzmonte, Sep 18, 2006
  10. hzmonte

    hzmonte Guest

    After reading all the related posts in this newsgroup (it turns out
    that there are other threads on this subject - just search for "const
    poisoning") so far I have felt that this is the most satisfactory
    hzmonte, Sep 18, 2006
  11. hzmonte

    Michael Mair Guest

    You snipped a little bit too much context:
    - as I understood you, you wanted to store a copy of a const void *
    which itself was not to be const void * but void *; afterwards,
    outside the function where you stored the copy, you wanted to modify
    the storage through this copy.
    - As the compiler correctly did give you a diagnostic, you
    complained that this seems wrong.
    - It seemed to me that you were not aware of const semantics

    Thus my statement.
    True; however, this is the only conceptually clean solution
    I know. With using "const void *" and 'casting away' const,
    you always risk trying to modify a truly const object that
    somehow got in there...

    Michael Mair, Sep 18, 2006
  12. hzmonte

    hzmonte Guest

    I am not saying the compiler is wrong. I am just saying that the C
    language design seems to be caught in two conflicting goals:
    1. Type safety: a "const type *" cannot be assigned to a "type *", for
    example. A cast is needed to subvert it.
    2. Invariance safety: A parameter that is not modified in a function
    can be qualified with 'const' and enforced by the compiler.
    Both goals are desirable. And the compiler correctly gives me a
    diagnostic when I violate the type safety rule. I am just looking for
    a way that I can program it in a way that both goals can be satisfied
    at the same time.
    OK, then let me say this: There are at least 3 ways to do it, each has
    its benefits and drawbacks:
    1. Not using 'const' at all: the drawback is that there is no
    "invariance safety" (I coined this term).
    2. Casting away the 'const': type safety is subverted.
    3. Making a copy of the data: Additional copying and memory is needed.
    hzmonte, Sep 19, 2006
  13. hzmonte

    hzmonte Guest

    To facilitate discussion, a copy of the "Rationale for International
    Standard - Programming Languages -C Revision 5.10 April-2003" can be
    at:http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf :
    Clause 6.7.3 Type qualifiers:
    The C89 Committee added two type qualifiers, const and volatile ...
    Individually and in combination they specify the assumptions a compiler
    can and must make when accessing an object an lvalue.
    The syntax and semantics of const were adapted from C++; the concept
    itself has appeared in other langauges. ...
    Type qualifiers were introduced in part to provide greater control over
    optimization. ...
    The basic qualifiers can be characreized 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.
    .... const is specified in such a way that an implementation is at
    liberty to put const objects in read-only storage, and is encouraged to
    diagnose obvious attempts to modify them, but is not required to trace
    down all the subtle ways that such checking can be subverted.
    1. The document does not explicitly address the use of 'const' to
    qualify a parameter of a fucntion. I am not able to find any rationale
    for using 'const' in the C++ Standard either. Maybe the justification
    is buried in some committee minutes.
    2. Though many people cite the desirability of making clear the fact
    that the parameter is not changed in the function if it is
    const-qualified, from the standpoint of safety and maintainability,
    this benefit is not mentioned in the document as a rationale for
    including 'const' in the C syntax. And there seems to be different
    opinion as to the feasibility/effectiveness of optimizing the code when
    a parameter is const-ified.

    I am starting to think that maybe const, as used to qualify a function
    parameter, cannot be used to satisfy two goals (type safety and
    invariance safety) at the same time. Perhaps two different constructs
    are needed to satisfy two different goals. For example, a new keyword
    'in' - like the one used in Ada to designate an input parameter, is
    needed to designate a parameter that is not changed in a function. So
    in my enqueue() example, the function would be like this:
    void enqueue(t_fifoQueue *q, in void *elem)
    t_fifoItem *i, *last;

    i = malloc(sizeof(t_fifoItem));
    i->elem = elem;
    last = q->prev;
    last->next = i;
    i->next = q;
    q->prev = i;
    And 'in' does not play a part in type compatibility; so "i->elem =
    elem" is okay.
    (And 'in' is not meant to solve the 'const poisoning' problem - that's
    a separate issue, involving whether the benefit of doing it is worth
    the trouble of cascading its use.)

    For those interested, I found many other threads in this newsgroup that
    are related to this subject - I never thought this is such a hot topic.
    They are with the subjects "Whats the deal with 'const'?" " to const
    or not to const " "is const useless and dangerous?" "is const useless
    and dangerous?" "ptr to const params to show "read only"" "function
    (const char *) vs. function (char *)" "compatible types in assignment"
    "problem with constant pointer...". Most, if not all, of these
    discussed the usefulness of 'const' (the last discussion thread raises
    question of whether casting away the 'const' would induce undefined
    behavior) but stopped short of really identifying the conceptual issue
    to my satisfaction (not the practical issue of cascading the const).
    (There is yet another lengthy discussion thread with the subject "What
    Value Does Const Correctness Offer" in a C++ forum at
    hzmonte, Sep 19, 2006
  14. hzmonte

    hzmonte Guest

    Let me refine my arguments. Two means, among others, to ensure safety
    1. Immutability on assignment - A variable of type "const float *" is
    not assignable to a variable of type "float *", for example.
    2. Immutability on parameter passing - A parameter qualified with
    'const' is not modifiable inside a function.
    Apparently there needs to be two different constructs to achieve these
    means. Using the same 'const' to achieve them creates conflicts.
    hzmonte, Sep 19, 2006
  15. Ugh? How would it differ from:

    void enqueue(t_fifoQueue *q, void *const elem) {

    Secondly, I don't see the use of the keyword "in" or "const" in such
    situation. As a programmer who uses given function, I do not care if
    function changes the value of argument or not since the changes don't
    affect outside of the function.
    Michal Nazarewicz, Sep 19, 2006
  16. hzmonte

    hzmonte Guest

    Ugh? How would it differ from:
    What I meant by "in void *elem" is that *elem is not changed. "void
    *const elem" means elem is not changed, but *elem may be changed.
    Well, that is a hot topic for debate. As I said, there are tons of
    discussions on the subject of the usefulness of const. Here, I am
    assuming it is useful to designate an input parameter as such. Of
    course if this assumption does not hold, it is moot to discuss whether
    'in' is better than 'const'.
    hzmonte, Sep 19, 2006
  17. Yes, exactly. That's because *elem will be changed by another
    function which takes t_fifoQueue as an argument. You can't claim that
    *elem won't be changed and then change it in another function.
    Adding new keyword which doesn't provide anything new is definitly not
    a good idea.
    Michal Nazarewicz, Sep 19, 2006
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.