The void** pointer breaking symmetry?

Discussion in 'C Programming' started by elmar@cmbi.ru.nl, May 5, 2006.

  1. Guest

    Hi Clers,

    If I look at my ~200000 lines of C code programmed over the past 15
    years, there is one annoying thing in this smart language, which
    somehow reduces the 'beauty' of the source code ;-):

    char *cp;
    void *vp;
    void **vpp;

    // 1
    cp=vp;

    // 2
    cp=*vpp;

    Why is the first instruction allowed while the second one creates a
    compiler warning/error?
    If vpp is a pointer to a void pointer, why am I not allowed to assign
    the content of vpp to a char pointer without ugly explicit casts? Why
    is it needed to break the symmetry? Are there any GCC compiler options
    to specifically disable this warning which doesn't make sense to me?

    Thanks for your feedback,
    Elmar

    P.S.: In case this is an old question: Googles inability to search for
    'void**' made it hard to find the answer ;-)
     
    , May 5, 2006
    #1
    1. Advertising

  2. wrote:
    > If I look at my ~200000 lines of C code programmed over the past 15
    > years, there is one annoying thing in this smart language, which
    > somehow reduces the 'beauty' of the source code ;-):
    >
    > char *cp;
    > void *vp;
    > void **vpp;
    >
    > // 1
    > cp=vp;
    >
    > // 2
    > cp=*vpp;
    >
    > Why is the first instruction allowed while the second one creates a
    > compiler warning/error?
    > If vpp is a pointer to a void pointer, why am I not allowed to assign
    > the content of vpp to a char pointer without ugly explicit casts? Why
    > is it needed to break the symmetry? Are there any GCC compiler options
    > to specifically disable this warning which doesn't make sense to me?
    >

    This looks surprisingly similar to a discussion in van der Linden's
    "Expert C Programming". There is a particular part in the Standard
    regarding assignments that talks about specific constraints. I don't
    have either handy right now, but the compiler error in this case feels
    right.
     
    void * clvrmnky(), May 5, 2006
    #2
    1. Advertising

  3. Eric Sosman Guest

    wrote On 05/05/06 14:44,:
    > Hi Clers,
    >
    > If I look at my ~200000 lines of C code programmed over the past 15
    > years, there is one annoying thing in this smart language, which
    > somehow reduces the 'beauty' of the source code ;-):
    >
    > char *cp;
    > void *vp;
    > void **vpp;
    >
    > // 1
    > cp=vp;
    >
    > // 2
    > cp=*vpp;
    >
    > Why is the first instruction allowed while the second one creates a
    > compiler warning/error?


    Both are legal, assuming vp has a valid value in the
    first instance and vpp points to a void* with a valid
    value in the second.

    Perhaps the compiler is warning about the `=*', which
    was an antique form of the operator now spelled `*='. If
    so, it's trying to tell you that `=*' no longer means what
    it did in the very early days of C (just in case you're
    compiling some very old code), and you can probably silence
    the warning by writing `= *' instead.

    There are no guarantees, though: The compiler is allowed
    to issue as many warnings as it wants, even for constructs
    that are well-defined. Most people consider this helpful
    in cases like

    if (a = b)
    printf ("Equal\n");
    else
    printf ("Unequal\n"); /* Not any more ... */

    .... which is a perfectly valid C fragment, but also a common
    slip of the finger.

    > If vpp is a pointer to a void pointer, why am I not allowed to assign
    > the content of vpp to a char pointer without ugly explicit casts?


    The assignment is allowed, and no cast is required.

    > Why
    > is it needed to break the symmetry? Are there any GCC compiler options
    > to specifically disable this warning which doesn't make sense to me?


    Perhaps if you'd show us "this warning" instead of making
    everybody guess about it, someone would have an idea.

    --
     
    Eric Sosman, May 5, 2006
    #3
  4. Ben Pfaff Guest

    writes:

    > char *cp;
    > void *vp;
    > void **vpp;
    >
    > // 1
    > cp=vp;
    >
    > // 2
    > cp=*vpp;
    >
    > Why is the first instruction allowed while the second one creates a
    > compiler warning/error?


    Both are allowed. If the compiler refuses to allow one of them
    (possibly with a warning) then you are probably using a C++
    compiler.
    --
    "Your correction is 100% correct and 0% helpful. Well done!"
    --Richard Heathfield
     
    Ben Pfaff, May 5, 2006
    #4
  5. Guest

    Big apologies, I accidentally pasted only the 'correct' of the two
    symmetry related cases:

    Here is the complete code:

    char *cp;
    void *vp;
    void **vpp;

    // 1
    cp=vp;
    // 2
    cp=*vpp;

    // 3
    vp=cp;
    // 4
    vpp=&cp;


    In short: if 1 and 2 work, why does the reversion work in case 3 but
    fail in case 4 with the GCC message "warning: assignment from
    incompatible pointer type" ?

    Thanks again,
    Elmar
     
    , May 5, 2006
    #5
  6. CBFalconer Guest

    wrote:
    >
    > If I look at my ~200000 lines of C code programmed over the past 15
    > years, there is one annoying thing in this smart language, which
    > somehow reduces the 'beauty' of the source code ;-):
    >
    > char *cp;
    > void *vp;
    > void **vpp;
    >
    > // 1
    > cp=vp;
    >
    > // 2
    > cp=*vpp;
    >
    > Why is the first instruction allowed while the second one creates a
    > compiler warning/error?
    > If vpp is a pointer to a void pointer, why am I not allowed to assign
    > the content of vpp to a char pointer without ugly explicit casts? Why
    > is it needed to break the symmetry? Are there any GCC compiler options
    > to specifically disable this warning which doesn't make sense to me?


    You are probably confusing errors, because the above dereferences
    an undefined pointer. The following is error free, and does not
    dereference undefined objects:

    int main(void) {

    char *cp;
    void *vp;
    void **vpp;
    char *s = "junk";

    vp = &s[0];
    cp = vp;
    vpp = &vp;
    cp = *vpp;

    return 0;
    }

    Always publish complete compileable source, so criticism can be
    meaningful.

    --
    "If you want to post a followup via groups.google.com, don't use
    the broken "Reply" link at the bottom of the article. Click on
    "show options" at the top of the article, then click on the
    "Reply" at the bottom of the article headers." - Keith Thompson
    More details at: <http://cfaj.freeshell.org/google/>
    Also see <http://www.safalra.com/special/googlegroupsreply/>
     
    CBFalconer, May 5, 2006
    #6
  7. writes:
    > Big apologies, I accidentally pasted only the 'correct' of the two
    > symmetry related cases:
    >
    > Here is the complete code:
    >
    > char *cp;
    > void *vp;
    > void **vpp;
    >
    > // 1
    > cp=vp;
    > // 2
    > cp=*vpp;
    >
    > // 3
    > vp=cp;
    > // 4
    > vpp=&cp;


    No, that's not the complete code. If I want to try compiling it
    myself, I have to wrap it in a function definition.

    Also, though "//" comments are legal in C99, they aren't supported by
    all C compilers, and they can cause problems on Usenet (wrapping of
    long lines can introduce syntax errors).

    Here's a complete program that illustrates the point:

    int main(void)
    {
    char *cp;
    void *vp;
    void **vpp;

    /* 1 */
    cp=vp;

    /* 2 */
    cp=*vpp;

    /* 3 */
    vp=cp;

    /* 4 */
    vpp=&cp; /* this is line 17 */

    return 0;
    }

    When I compile this with gcc, I get:

    tmp.c: In function `main':
    tmp.c:17: warning: assignment from incompatible pointer type

    > In short: if 1 and 2 work, why does the reversion work in case 3 but
    > fail in case 4 with the GCC message "warning: assignment from
    > incompatible pointer type" ?


    Case 1 assigns a void* to a char*. Implicit conversion from void* to
    any pointer-to-object type makes this legal.

    Case 2 also assigns a void* to a char*.

    Case 3 assigns a char* to a void*. Implicit conversion from any
    pointer-to-object type to void* makes this legal.

    Case 4 assigns a char** to a void**. The implicit conversion rule
    applies only to void*, not to void**. In this context, a void* is
    just another object type, and a void** is just another
    pointer-to-object type. There's no implicit conversion from one
    pointer-to-object type to another pointer-to-object type.

    void* is a generic pointer type. There is no generic
    pointer-to-pointer type.

    This is similar to the fact that there's an implicit conversion
    between int and double, but no implicit conversion between int* and
    double*.

    If you want a generic pointer, just use a void*. For your case 4,
    this would be legal:

    vp = &cp;

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
    We must do something. This is something. Therefore, we must do this.
     
    Keith Thompson, May 6, 2006
    #7
  8. Guest

    Thanks for your detailed reply, Keith.

    In my humble view, the ideal solution would be:

    1) void* is a generic pointer type that can be implicitly converted
    to/from any other object that can be dereferenced at least !once!
    (e.g. char*, int*, also char**, int**, but not char,int)
    2) void** is a generic pointer type that can be implicitly converted
    to/from any other object that can be dereferenced at least !twice!
    (e.g. char**, int**, also char***, int***, but not char*,int*,char,int)
    3) etc...

    Again, I am not one of those who like to (have the time to) discuss
    philosophic questions about language details. This is a purely
    practical issue, having identified the main cause of entropy (and also
    crashes ;-) in my current sources: The inability to safely pass a
    pointer to any pointer as a function argument.

    Typical example: the function mem_freesetnull which frees a pointer and
    sets it to NULL (very helpful in the context of exception handling):

    Ideally, it would take the address of a pointer as argument and look
    like that:

    void mem_freesetnull(void **ptradd)
    { mem_free(*ptradd);
    *ptradd=NULL; }

    Unfortunately, that's not possible, because I'd have to use an ugly
    explicit cast to (void**) in every call to the function.

    So in practice, I have to move the explicit cast to the function
    itself:

    void mem_freesetnull(void *ptradd)
    { mem_free(*(void**)ptradd);
    *(void**)ptradd=NULL; }

    Now the function looks ugly, but more importantly, I lost an important
    piece of type-safety:
    If I accentally forget the reference operator & in the function call,
    noone will complain but the program will crash:

    char *cp;
    cp=mem_alloc(1000);
    mem_freesetnull(cp); /* Crash! */

    With the approach suggested above, the compiler would immediately
    identify the problem, since cp cannot be dereferenced at least 2 times,
    as required by the ideal function declaration
    void mem_freesetnull(void **ptradd).

    In short: less code entropy and more safety in one shot. It seems that
    others are bothered by the same thing, since this in the FAQ:

    4.9: Can I use a void ** pointer as a parameter so that a function
    can accept a generic pointer by reference?

    A: Not portably.

    (not sure what the answer means in this context. Does it cause problems
    on a VAX from 1968? ;-)

    I'm thinking about a GCC patch for an option to specifically disable
    the warning in the cases outlined above. But if that has zero chance of
    acceptance, I'll save my time ;-)

    Ciao and thanks,
    Elmar
     
    , May 6, 2006
    #8
  9. Michael Mair Guest

    schrieb:
    > Thanks for your detailed reply, Keith.


    Of which you unfortunately did not quote anything to provide
    context.

    > In my humble view, the ideal solution would be:
    >
    > 1) void* is a generic pointer type that can be implicitly converted
    > to/from any other object that can be dereferenced at least !once!
    > (e.g. char*, int*, also char**, int**, but not char,int)
    > 2) void** is a generic pointer type that can be implicitly converted
    > to/from any other object that can be dereferenced at least !twice!
    > (e.g. char**, int**, also char***, int***, but not char*,int*,char,int)
    > 3) etc...
    >
    > Again, I am not one of those who like to (have the time to) discuss
    > philosophic questions about language details. This is a purely
    > practical issue, having identified the main cause of entropy (and also
    > crashes ;-) in my current sources: The inability to safely pass a
    > pointer to any pointer as a function argument.


    The problem with this approach is that if, for example, char *foo,
    int *bar, and struct qux *quux have different sizes, alignment
    requirements and representations, then there is no way that
    void **baz (containing either the address of foo, bar, or quux, or
    of a variable containing their addresses, or ...) helps you
    deal with the object(s) they point to if you pass the addresses of
    such pointers, because dereferencing the void ** variable once gives
    you at least three possible interpretations of the bit pattern
    involved. The only safe way to obtain the address of the object would
    be if *baz would evaluate to a type which can contain any address that
    can be pointed to by any of, foo, bar and quux. One such type is void*.
    So,
    void *aux = bar;
    void **baz = &aux;
    essentially is the best you can get if you do not want the language
    to have to "remember" types at runtime.


    > Typical example: the function mem_freesetnull which frees a pointer and
    > sets it to NULL (very helpful in the context of exception handling):
    >
    > Ideally, it would take the address of a pointer as argument and look
    > like that:
    >
    > void mem_freesetnull(void **ptradd)
    > { mem_free(*ptradd);
    > *ptradd=NULL; }
    >
    > Unfortunately, that's not possible, because I'd have to use an ugly
    > explicit cast to (void**) in every call to the function.
    >
    > So in practice, I have to move the explicit cast to the function
    > itself:
    >
    > void mem_freesetnull(void *ptradd)
    > { mem_free(*(void**)ptradd);
    > *(void**)ptradd=NULL; }
    >
    > Now the function looks ugly, but more importantly, I lost an important
    > piece of type-safety:
    > If I accentally forget the reference operator & in the function call,
    > noone will complain but the program will crash:
    >
    > char *cp;
    > cp=mem_alloc(1000);
    > mem_freesetnull(cp); /* Crash! */


    Even
    mem_freesetnull(&cp)
    works portably only due to special guarantees for char*.
    If you did the same for int *ip or struct qux *sp, you could run
    into trouble on a system where not all pointers are equal.


    > With the approach suggested above, the compiler would immediately
    > identify the problem, since cp cannot be dereferenced at least 2 times,
    > as required by the ideal function declaration
    > void mem_freesetnull(void **ptradd).


    Not portably if you do not want to change the information the
    programme must have at runtime -- and then you could change other
    restrictions of C as well.

    > In short: less code entropy and more safety in one shot.


    At a very high price for the language.

    In addition, not every place the now-stale address is stored will
    be "nulled" by this function -- it is more or less a false sense
    of safety. Realloc()ing to zero or even to a smaller size of the
    originally malloc()ed storage gives you similar headaches.

    Having a way to mark the starting address of the allocated object
    and all addresses inside or one past the object as trap
    representation with ways to find out where the whole thing trapped
    would be much more useful.
    Malloc debugging tools already do at least part of the job for you.
    Just add "runs cleanly under <YourToolHere> for the test set" after
    "compiles without warning" and "is <YourLintToolHere>-clean".


    > It seems that
    > others are bothered by the same thing, since this in the FAQ:
    >
    > 4.9: Can I use a void ** pointer as a parameter so that a function
    > can accept a generic pointer by reference?
    >
    > A: Not portably.
    >
    > (not sure what the answer means in this context. Does it cause problems
    > on a VAX from 1968? ;-)
    >
    > I'm thinking about a GCC patch for an option to specifically disable
    > the warning in the cases outlined above. But if that has zero chance of
    > acceptance, I'll save my time ;-)


    As I did not read what you really want to achieve (you snipped the
    context), I do not know whether this is the perfect solution for you
    or just the bad idea it seems to be...


    Cheers
    Michael
    --
    E-Mail: Mine is an /at/ gmx /dot/ de address.
     
    Michael Mair, May 6, 2006
    #9
  10. Eric Sosman Guest

    wrote:

    > Thanks for your detailed reply, Keith.
    >
    > In my humble view, the ideal solution would be:
    >
    > 1) void* is a generic pointer type that can be implicitly converted
    > to/from any other object that can be dereferenced at least !once!
    > (e.g. char*, int*, also char**, int**, but not char,int)
    > 2) void** is a generic pointer type [...]


    No; stop right there. void** is not at "generic" at all,
    not in the least. A void** is a pointer to a void*, and not
    a pointer to any other kind of object or function.

    Your confusion, perhaps, is this: A void* can point to any
    kind of object, and can be converted to and from other object
    pointer types without a cast. That's why it's often called
    "generic," but the term is really very loose. However, a void*
    is itself an object type, a perfectly concrete "real" object
    type like an int or a char* or whatever. Just as with other
    concrete object types, it's possible to form a pointer to objects
    of this void* type. But such a pointer is in no way "generic;"
    it can only be NULL or point to an actual void* object somewhere.

    > [...] having identified the main cause of entropy (and also
    > crashes ;-) in my current sources: The inability to safely pass a
    > pointer to any pointer as a function argument.


    That's right. C does not require that all pointers "smell
    the same." Pointers to different types can come in different
    shapes and sizes, so there's really no such thing as a "generic
    pointer" (despite the common sloppy usage of the phrase to
    describe void*). You might as well speak of a "generic number;"
    just as short and double can look different, short* and double*
    can look different.

    > Typical example: the function mem_freesetnull which frees a pointer and
    > sets it to NULL (very helpful in the context of exception handling):
    > Ideally, it would take the address of a pointer as argument and look
    > like that:
    >
    > void mem_freesetnull(void **ptradd)
    > { mem_free(*ptradd);
    > *ptradd=NULL; }
    >
    > Unfortunately, that's not possible, because I'd have to use an ugly
    > explicit cast to (void**) in every call to the function.


    Even the cast will not save you. Just as numbers come in
    different flavors, pointers come in different flavors. Just as
    you cannot set a number to zero without knowing its type, you
    cannot set a pointer to NULL without knowing its type.

    > So in practice, I have to move the explicit cast to the function
    > itself:
    >
    > void mem_freesetnull(void *ptradd)
    > { mem_free(*(void**)ptradd);
    > *(void**)ptradd=NULL; }
    >
    > Now the function looks ugly, but more importantly, I lost an important
    > piece of type-safety:


    Most important of all, the function is now incorrect.
    You seem upset by all the warnings the compilers emit for
    constructs of this sort, but it turns out they know C better
    than you do: This code is wrong, and the compiler is right
    to complain about it.

    > I'm thinking about a GCC patch for an option to specifically disable
    > the warning in the cases outlined above. But if that has zero chance of
    > acceptance, I'll save my time ;-)


    While you're at it, disable the diagnostics for unbalanced
    parentheses, for `("Hello" / "world!")', and for all the other
    programming errors that might be made. The source for gcc is
    readily available; you are free to make changes and use your own
    version if you choose -- but allow me to suggest that your choice
    is ill-informed. In short, you don't know what you're doing; you
    don't know C well enough.

    --
    Eric Sosman
    lid
     
    Eric Sosman, May 6, 2006
    #10
  11. Eric Sosman <> writes:
    > wrote:

    [...]
    >> Typical example: the function mem_freesetnull which frees a pointer and
    >> sets it to NULL (very helpful in the context of exception handling):
    >> Ideally, it would take the address of a pointer as argument and look
    >> like that:
    >> void mem_freesetnull(void **ptradd)
    >> { mem_free(*ptradd);
    >> *ptradd=NULL; }
    >> Unfortunately, that's not possible, because I'd have to use an ugly
    >> explicit cast to (void**) in every call to the function.

    >
    > Even the cast will not save you. Just as numbers come in
    > different flavors, pointers come in different flavors. Just as
    > you cannot set a number to zero without knowing its type, you
    > cannot set a pointer to NULL without knowing its type.


    And even though many actual implementations implement all pointer
    types the same way, the standard is carefully designed to allow
    different pointer types to have different representations. For
    example, on a system that only has word pointers in hardware, a char*
    or void* (pointing to a subset of a word) might need extra bits to
    indicate an offset within the word.

    Conversion between void* and int* can be done because the compiler
    knows how to convert from one representation to another. (The fact
    that the conversion can be done implicitly isn't really relevant here;
    it's just a convenience.) Conversion between void** and int** can
    work only if void* and int* have the same representation, something
    that's commonly true but is not guaranteed by the language.

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
    We must do something. This is something. Therefore, we must do this.
     
    Keith Thompson, May 6, 2006
    #11
  12. Guest

    > > In my humble view, the ideal solution would be:
    >
    > > 1) void* is a generic pointer type that can be implicitly converted
    > > to/from any other object that can be dereferenced at least !once!
    > > (e.g. char*, int*, also char**, int**, but not char,int)
    > > 2) void** is a generic pointer type [...]

    >
    > No; stop right there. void** is not at "generic" at all,
    > not in the least. A void** is a pointer to a void*, and not
    > a pointer to any other kind of object or function.
    >
    > Your confusion, perhaps, is this: A void* can point to any
    > kind of object, and can be converted to and from other object
    > pointer types without a cast. That's why it's often called
    > "generic," but the term is really very loose. However, a void*
    > is itself an object type, a perfectly concrete "real" object
    > type like an int or a char* or whatever. Just as with other
    > concrete object types, it's possible to form a pointer to objects
    > of this void* type. But such a pointer is in no way "generic;"
    > it can only be NULL or point to an actual void* object somewhere.


    I am not sure about my level of confusion, I mainly encountered a
    language feature that didn't 'feel' right (=efficient), so I asked
    for the reason. Now I know that it's caused by exotic architectures
    where pointers are not all guaranteed to have the same size, and
    that the language pays the price of not being able to use a pointer
    to any pointer as a function argument in one shot. You manually have
    to cast your pointer to void* and back before and after the function,
    requiring 3 lines instead of 1 line code. Thanks also for the other
    similar answers from Keith Thompson and Michael Mair.

    >
    > > [...] having identified the main cause of entropy (and also

    >
    > > crashes ;-) in my current sources: The inability to safely pass a
    > > pointer to any pointer as a function argument.

    >
    > That's right. C does not require that all pointers "smell
    > the same." Pointers to different types can come in different
    > shapes and sizes, so there's really no such thing as a "generic
    > pointer" (despite the common sloppy usage of the phrase to
    > describe void*). You might as well speak of a "generic number;"
    > just as short and double can look different, short* and double*
    > can look different.
    >
    > > Typical example: the function mem_freesetnull which frees a pointer and
    > > sets it to NULL (very helpful in the context of exception handling):
    > > Ideally, it would take the address of a pointer as argument and look
    > > like that:

    >
    > > void mem_freesetnull(void **ptradd)
    > > { mem_free(*ptradd);
    > > *ptradd=NULL; }

    >
    > > Unfortunately, that's not possible, because I'd have to use an ugly
    > > explicit cast to (void**) in every call to the function.

    >
    > Even the cast will not save you. Just as numbers come in
    > different flavors, pointers come in different flavors. Just as
    > you cannot set a number to zero without knowing its type, you
    > cannot set a pointer to NULL without knowing its type.
    >
    > > So in practice, I have to move the explicit cast to the function
    > > itself:

    >
    > > void mem_freesetnull(void *ptradd)
    > > { mem_free(*(void**)ptradd);
    > > *(void**)ptradd=NULL; }

    >
    > > Now the function looks ugly, but more importantly, I lost an important
    > > piece of type-safety:

    >
    > Most important of all, the function is now incorrect.
    > You seem upset by all the warnings the compilers emit for
    > constructs of this sort, but it turns out they know C better
    > than you do: This code is wrong, and the compiler is right
    > to complain about it.
    >
    > > I'm thinking about a GCC patch for an option to specifically disable
    > > the warning in the cases outlined above. But if that has zero chance of
    > > acceptance, I'll save my time ;-)

    >
    > While you're at it, disable the diagnostics for unbalanced
    > parentheses, for `("Hello" / "world!")', and for all the other
    > programming errors that might be made. The source for gcc is
    > readily available; you are free to make changes and use your own
    > version if you choose -- but allow me to suggest that your choice
    > is ill-informed. In short, you don't know what you're doing; you
    > don't know C well enough.
    >


    I agree that mentioning a GCC patch was a bit provocative, sorry that I
    hurt
    your feelings. Nevertheless your comment and the emphasis you put on it
    nicely illustrates that there are two types of programming brains:

    #define FOOS 10
    struct
    { int i;
    double d;
    char *p; } *foo;

    foo=calloc(FOOS,sizeof(*foo));

    Looking at the piece of code above, brain type A will cry in pain
    and quickly change the wrong code to read

    foo=malloc(FOOS*sizeof(*foo));
    for (i=0;i<FOOS;i++)
    { foo.i=0;
    foo.d=0;
    foo.p=NULL; }

    since there are architectures where a NULL pointer doesn't have all
    bits at 0, so calloc essentially leaves garbage, and maybe there are
    also architectures where floating point numbers are represented
    differently,
    so we also have to set foo.d to 0, and while we are at it, someone
    might on day design an architecure where ints also have an unusual
    representation, so we do the same for foo.i (or are we lucky and the
    standard somewhere says that an integer 0 must be a 0 in memory? ;-)

    And then, there's brain type B who sees that the 'correct' version
    takes
    5 lines instead of 1, reduces readability, maintainability, can be
    a source of additional errors and wastes developer life time.
    Type B simply uses the 'elegant' calloc approach and trusts in the
    laws of physics: even if one night, someone drunk and stoned
    creates an architecture that requires the long version,
    it will be forced to play a marginal niche role, since an architecture
    that is so inherently inefficient that it requires five times as much
    code cannot rule the world. Even if Microsoft supports it and tricks
    the C standard committee to do the same ;-).

    In short, the reason for the success of C is that it gives its users
    complete freedom: Brain type A can happily write standard-conform
    code that is cluttered but fully portable to the 'CPU' in his/her
    vacuum cleaner (and will never know the function mem_freesetnull),
    while brain type B can write 'elegant' code that only works on
    'elegant' architectures - but given my many inline Assembly functions,
    that's the least of my concerns. So I will just do what brain types B
    do:
    hide the traces of dirt left in the language by dirty architectures
    behind
    a macro and keep my mouth shut ;-)

    Thanks again for all your comments, I guess everything has been said..

    Cheers,
    Elmar
     
    , May 8, 2006
    #12
  13. CBFalconer Guest

    wrote:
    >

    .... snip ...
    >
    > #define FOOS 10
    > struct
    > { int i;
    > double d;
    > char *p; } *foo;
    >
    > foo=calloc(FOOS,sizeof(*foo));
    >
    > Looking at the piece of code above, brain type A will cry in pain
    > and quickly change the wrong code to read
    >
    > foo=malloc(FOOS*sizeof(*foo));
    > for (i=0;i<FOOS;i++)
    > { foo.i=0;
    > foo.d=0;
    > foo.p=NULL; }
    >
    > since there are architectures where a NULL pointer doesn't have all
    > bits at 0, so calloc essentially leaves garbage, and maybe there are
    > also architectures where floating point numbers are represented
    > differently,


    I hope not. The intelligent coder will want things to work, so he
    will first test the result of the malloc. Then he might well have
    other cases in which a foothing needs to be initialiazed, so he
    would make the definitions more usable:

    struct foo {
    int i;
    double d;
    char *p;
    } *foop;

    void initfoo(struct foo *foop) {
    foop->i = foop->d = 0; foop->p = NULL;
    } /* initfoo */

    ....
    if (foop = malloc(FOOS * sizeof *foop))
    for (i = 0; i < FOOS; i++) initfoo(foop);
    else
    /* take corrective action on memory failure */;

    .... snip ...
    >
    > And then, there's brain type B who sees that the 'correct' version
    > takes
    > 5 lines instead of 1, reduces readability, maintainability, can be
    > a source of additional errors and wastes developer life time.


    Sloppy B simply ignores the problem, and leaves the mess for others
    to clean up. He doesn't care if the intermediate consequences are
    fatal to anything. He is a certified idiot of little brain. He
    doesn't even have the excuse of ignorance.

    --
    "They that can give up essential liberty to obtain a little
    temporary safety deserve neither liberty nor safety."
    -- B. Franklin, 1759
    "When I want to make a man look like an idiot, I quote him."
    -- Mark Twain
    "I hear the voices." G W Bush, 2006-04-18
     
    CBFalconer, May 8, 2006
    #13
  14. Eric Sosman Guest

    wrote On 05/08/06 02:41,:
    > [...] there are two types of programming brains:
    >
    > #define FOOS 10
    > struct
    > { int i;
    > double d;
    > char *p; } *foo;
    >
    > foo=calloc(FOOS,sizeof(*foo));
    >
    > Looking at the piece of code above, brain type A will cry in pain
    > and quickly change the wrong code to read
    >
    > foo=malloc(FOOS*sizeof(*foo));
    > for (i=0;i<FOOS;i++)
    > { foo.i=0;
    > foo.d=0;
    > foo.p=NULL; }
    >
    > since there are architectures where a NULL pointer doesn't have all
    > bits at 0, so calloc essentially leaves garbage, and maybe there are
    > also architectures where floating point numbers are represented
    > differently,
    > so we also have to set foo.d to 0, and while we are at it, someone
    > might on day design an architecure where ints also have an unusual
    > representation, so we do the same for foo.i (or are we lucky and the
    > standard somewhere says that an integer 0 must be a 0 in memory? ;-)
    >
    > And then, there's brain type B who sees that the 'correct' version
    > takes
    > 5 lines instead of 1, reduces readability, maintainability, can be
    > a source of additional errors and wastes developer life time.


    You've overlooked brain type C, belonging to programmers
    who don't allocate memory until there's something they want
    to place in it. Such programmers seldom want to "clear" a
    piece of allocated memory to anything in particular, either
    with calloc() or with memset() or with a loop. Rather, they'll
    use the newly-allocated memory to store whatever it was they
    allocated it to hold. Filling a block of memory with zeroes
    (of whatever kind) usually doesn't advance the state of the
    computation very far.

    Another thing about type C programmers: They never write
    a malloc() or calloc() or realloc() call without also writing
    an `if' ...

    --
     
    Eric Sosman, May 8, 2006
    #14
  15. Guest

    > > #define FOOS 10
    > > struct
    > > { int i;
    > > double d;
    > > char *p; } *foo;

    >
    > > foo=calloc(FOOS,sizeof(*foo));

    >
    > > Looking at the piece of code above, brain type A will cry in pain
    > > and quickly change the wrong code to read

    >
    > > foo=malloc(FOOS*sizeof(*foo));
    > > for (i=0;i<FOOS;i++)
    > > { foo.i=0;
    > > foo.d=0;
    > > foo.p=NULL; }

    >
    > > since there are architectures where a NULL pointer doesn't have all
    > > bits at 0, so calloc essentially leaves garbage, and maybe there are
    > > also architectures where floating point numbers are represented
    > > differently,

    >
    > I hope not. The intelligent coder will want things to work, so he
    > will first test the result of the malloc.


    Indeed, I am not used to the 'brain type A' way of thinking where it's
    common to expand the source code with one additional 'if statement' per
    memory allocation. Of course the callocs/mallocs above are not those
    from libc. (Mine are called mem_alloc and mem_calloc, and I didn't want
    to cause additional confusion, but I'll do it from now ;-). Anyway,
    mem_alloc and friends do of course never return a NULL pointer.
    Instead, you can register emergency cleanup functions (that for example
    try to save any open documents) directly in the memory manager. Keep in
    mind that if the OS denies even small memory allocation requests, no
    program with a decent user interface can continue to do any kind of
    useful work. For truly big memory allocations, there's mem_allocnull
    and friends, that may return a NULL pointer, which you check and in the
    worst case tell the user that this specific operation needs more RAM or
    a bigger swap file.

    > Then he might well have
    > other cases in which a foothing needs to be initialiazed, so he
    > would make the definitions more usable:
    >
    > struct foo {
    > int i;
    > double d;
    > char *p;
    >
    > } *foop;
    >
    > void initfoo(struct foo *foop) {
    > foop->i = foop->d = 0; foop->p = NULL;
    >
    > } /* initfoo */
    >


    I don't agree. If you know the big picture in advance, you also know
    that a certain structure is guaranteed to be needed only once, and then
    the fooing above just costs your life time and randomizes the code
    order.

    > Sloppy B simply ignores the problem, and leaves the mess for others
    > to clean up. He doesn't care if the intermediate consequences are
    > fatal to anything. He is a certified idiot of little brain. He
    > doesn't even have the excuse of ignorance.


    Oups, you missed that at the end of the last posting, I identified
    myself as type B. So this insult is not anonymous, but hits me
    directly. Please apologize, or I won't reply anymore.

    Cheers,
    Elmar
     
    , May 8, 2006
    #15
  16. Guest

    Eric Sosman wrote:
    > wrote On 05/08/06 02:41,:
    > > [...] there are two types of programming brains:
    > >
    > > #define FOOS 10
    > > struct
    > > { int i;
    > > double d;
    > > char *p; } *foo;
    > >
    > > foo=calloc(FOOS,sizeof(*foo));
    > >
    > > Looking at the piece of code above, brain type A will cry in pain
    > > and quickly change the wrong code to read
    > >
    > > foo=malloc(FOOS*sizeof(*foo));
    > > for (i=0;i<FOOS;i++)
    > > { foo.i=0;
    > > foo.d=0;
    > > foo.p=NULL; }
    > >
    > > since there are architectures where a NULL pointer doesn't have all
    > > bits at 0, so calloc essentially leaves garbage, and maybe there are
    > > also architectures where floating point numbers are represented
    > > differently,
    > > so we also have to set foo.d to 0, and while we are at it, someone
    > > might on day design an architecure where ints also have an unusual
    > > representation, so we do the same for foo.i (or are we lucky and the
    > > standard somewhere says that an integer 0 must be a 0 in memory? ;-)
    > >
    > > And then, there's brain type B who sees that the 'correct' version
    > > takes
    > > 5 lines instead of 1, reduces readability, maintainability, can be
    > > a source of additional errors and wastes developer life time.

    >
    > You've overlooked brain type C, belonging to programmers
    > who don't allocate memory until there's something they want
    > to place in it. Such programmers seldom want to "clear" a
    > piece of allocated memory to anything in particular, either
    > with calloc() or with memset() or with a loop. Rather, they'll
    > use the newly-allocated memory to store whatever it was they
    > allocated it to hold. Filling a block of memory with zeroes
    > (of whatever kind) usually doesn't advance the state of the
    > computation very far.


    Brain type B is also efficient and only used calloc because foo.i and
    foo.d will both be used to calculate sums: foo.i will count apples in a
    basket, and foo.d will sum up the corresponding weights. foo.p will
    hold a list of the individual apple weights. I won't waste your time
    with the details how the list is managed, but I can tell you that is
    saves type B the burden and source code to actually create the list, if
    a function that appends something to a list first checks if the list
    pointer is NULL, and in this case creates the list before doing the
    work. Just like mem_realloc's ability to accept a NULL pointer saves
    you from using mem_alloc first.
    In short: mem_calloc was truly needed, otherwise type B would have
    certainly not done it ;-)

    >
    > Another thing about type C programmers: They never write
    > a malloc() or calloc() or realloc() call without also writing
    > an `if' ...


    Thanks for noting that this little detail was not the original topic
    and thus a simplification of little importance. See my other reply
    (explaining why type B doesn't use ifs in this context) to the guy who
    was less friendly..

    Ciao,
    Elmar
     
    , May 8, 2006
    #16
  17. CBFalconer Guest

    wrote:
    >

    .... snip ...
    >
    >> Sloppy B simply ignores the problem, and leaves the mess for others
    >> to clean up. He doesn't care if the intermediate consequences are
    >> fatal to anything. He is a certified idiot of little brain. He
    >> doesn't even have the excuse of ignorance.

    >
    > Oups, you missed that at the end of the last posting, I identified
    > myself as type B. So this insult is not anonymous, but hits me
    > directly. Please apologize, or I won't reply anymore.


    I addressed myself to the person who ignored the standards, and was
    sloppy. I have no control over to whom that applies.

    --
    "If you want to post a followup via groups.google.com, don't use
    the broken "Reply" link at the bottom of the article. Click on
    "show options" at the top of the article, then click on the
    "Reply" at the bottom of the article headers." - Keith Thompson
    More details at: <http://cfaj.freeshell.org/google/>
    Also see <http://www.safalra.com/special/googlegroupsreply/>
     
    CBFalconer, May 8, 2006
    #17
  18. Michael Mair Guest

    schrieb:
    >> > #define FOOS 10
    >> > struct
    >> > { int i;
    >> > double d;
    >> > char *p; } *foo;

    >>
    >> > foo=calloc(FOOS,sizeof(*foo));

    >>
    >> > Looking at the piece of code above, brain type A will cry in pain
    >> > and quickly change the wrong code to read

    >>
    >> > foo=malloc(FOOS*sizeof(*foo));
    >> > for (i=0;i<FOOS;i++)
    >> > { foo.i=0;
    >> > foo.d=0;
    >> > foo.p=NULL; }

    >>
    >> > since there are architectures where a NULL pointer doesn't have all
    >> > bits at 0, so calloc essentially leaves garbage, and maybe there are
    >> > also architectures where floating point numbers are represented
    >> > differently,

    >>
    >>I hope not. The intelligent coder will want things to work, so he
    >>will first test the result of the malloc.

    >
    > Indeed, I am not used to the 'brain type A' way of thinking where it's
    > common to expand the source code with one additional 'if statement' per
    > memory allocation. Of course the callocs/mallocs above are not those
    > from libc. (Mine are called mem_alloc and mem_calloc, and I didn't want
    > to cause additional confusion, but I'll do it from now ;-). Anyway,
    > mem_alloc and friends do of course never return a NULL pointer.
    > Instead, you can register emergency cleanup functions (that for example
    > try to save any open documents) directly in the memory manager. Keep in
    > mind that if the OS denies even small memory allocation requests, no
    > program with a decent user interface can continue to do any kind of
    > useful work. For truly big memory allocations, there's mem_allocnull
    > and friends, that may return a NULL pointer, which you check and in the
    > worst case tell the user that this specific operation needs more RAM or
    > a bigger swap file.


    Hmmm, and how do you make sure that you never inadvertently try to
    mem_alloc() too much because of an erroneous size computation
    beforehand?
    In addition, it causes less code changes if the requirements change:
    You already have your error handling code in place, have tested it
    once, have enough information (no additional parameters to wire the
    code with) so that you can change strategy, request a user input,
    die gracefully, or whatever is necessary.
    I have seen enough huge simulations die on the last couple of time
    steps without storing an intermediate state of computation before
    dieing for things like forgotten checks after demanding "only a couple
    of bytes from the heap"... Reading "paranoia check omitted for speedup"
    or similar makes you wish to throttle the culprit...

    I started out as type B and made my way over to something between type
    A and Eric's type C.
    Cleaner algorithms and less maintenance cost leave enough time for
    safety checks during run-time and during programming-time :)


    >>Then he might well have
    >>other cases in which a foothing needs to be initialiazed, so he
    >>would make the definitions more usable:
    >>
    >>struct foo {
    >> int i;
    >> double d;
    >> char *p;
    >>
    >>} *foop;
    >>
    >>void initfoo(struct foo *foop) {
    >> foop->i = foop->d = 0; foop->p = NULL;
    >>
    >>} /* initfoo */

    >
    > I don't agree. If you know the big picture in advance, you also know
    > that a certain structure is guaranteed to be needed only once, and then
    > the fooing above just costs your life time and randomizes the code
    > order.


    If it is needed only once, then there is no need for allocation.


    >>Sloppy B simply ignores the problem, and leaves the mess for others
    >>to clean up. He doesn't care if the intermediate consequences are
    >>fatal to anything. He is a certified idiot of little brain. He
    >>doesn't even have the excuse of ignorance.

    >
    > Oups, you missed that at the end of the last posting, I identified
    > myself as type B. So this insult is not anonymous, but hits me
    > directly. Please apologize, or I won't reply anymore.


    Well, even though Chuck tends to rather harsh wording, he has a
    point here:
    I'd rather work with type A or C than with type B as type B's
    inadvertent mistakes could cost _my_ lifetime and count against my
    frustration tolerance threshold when it comes to a customer fuming
    over a stupid mistake costing hours of work.
    If type B documented all his or her decisions for more efficient
    code together with some reasoning why it is safe to do here, then
    this would cost more time than doing all the "overhead stuff" in
    the first place.

    BTW: It is completely acceptable to make some assumptions like
    "8 bit bytes", "2s complement exact width integer types", "all
    pointer types have the same type and representation and alignment
    requirements", etc. -- if you document them and have a compile
    time test module and a run-time test module making sure these
    assumptions are justified. Then your code is an island of security.
    However, this means that even small parts of the code must be
    assumed to rely on these assumptions which may make the code
    unusable for projects without the respective assumptions.
    For small projects, rewriting may be a good idea; for large
    projects, you get your personal regression test nightmare.


    Cheers
    Michael
    --
    E-Mail: Mine is an /at/ gmx /dot/ de address.
     
    Michael Mair, May 8, 2006
    #18
  19. writes:
    > Eric Sosman wrote:

    [...]
    >> Another thing about type C programmers: They never write
    >> a malloc() or calloc() or realloc() call without also writing
    >> an `if' ...

    >
    > Thanks for noting that this little detail was not the original topic
    > and thus a simplification of little importance. See my other reply
    > (explaining why type B doesn't use ifs in this context) to the guy who
    > was less friendly..


    Whether it was the original topic or not, it's an important point, and
    it was a bug in the code you posted.

    If you want to be a sloppy programmer that's up to you. If you want
    to claim that you're doing so because you have a "type B brain",
    that's ok too. If you do it in this newsgroup, expect to be
    challenged on it.

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
    We must do something. This is something. Therefore, we must do this.
     
    Keith Thompson, May 8, 2006
    #19
  20. Ben C Guest

    ["Brain Type B":]

    > #define FOOS 10
    > struct
    > { int i;
    > double d;
    > char *p; } *foo;
    >
    > foo=calloc(FOOS,sizeof(*foo));


    > Looking at the piece of code above, brain type A will cry in pain
    > and quickly change the wrong code to read
    >
    > foo=malloc(FOOS*sizeof(*foo));
    > for (i=0;i<FOOS;i++)
    > { foo.i=0;
    > foo.d=0;
    > foo.p=NULL; }
    >


    > And then, there's brain type B who sees that the 'correct' version
    > takes 5 lines instead of 1, reduces readability, maintainability, can
    > be a source of additional errors and wastes developer life time. Type
    > B simply uses the 'elegant' calloc approach [...]


    Practically speaking it's much more likely that someone will add a field
    to the struct and forget to update the type A setup code (this happens
    really quite often) than that the code will get ported to a machine on
    which NULL pointers, 0 and 0.0 aren't all represented by zero bits.

    There's a saying "premature optimization is the root of all evil".
    Premature paranoia is pretty bad too. Anything, premature or not, that
    reduces readability or simplicity is going to introduce bugs, and many
    of those bugs will work every day on everyday machines.

    Sloppiness (e.g. not checking errors at all) is obviously unacceptable;
    but the naive conclusion that "the more error or paranoia checking the
    better" is almost as bad if it's done at the price of untested error
    recovery paths or complex and difficult-to-maintain initialization
    routines.
     
    Ben C, May 8, 2006
    #20
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ollej Reemt
    Replies:
    7
    Views:
    605
    Jack Klein
    Apr 22, 2005
  2. =?Utf-8?B?RXJpayBFVFM=?=

    A symmetry issue

    =?Utf-8?B?RXJpayBFVFM=?=, Jun 19, 2006, in forum: ASP .Net
    Replies:
    0
    Views:
    385
    =?Utf-8?B?RXJpayBFVFM=?=
    Jun 19, 2006
  3. Stig Brautaset

    `void **' revisited: void *pop(void **root)

    Stig Brautaset, Oct 25, 2003, in forum: C Programming
    Replies:
    15
    Views:
    840
    The Real OS/2 Guy
    Oct 28, 2003
  4. Replies:
    5
    Views:
    886
    S.Tobias
    Jul 22, 2005
  5. Replies:
    1
    Views:
    439
    Victor Bazarov
    May 23, 2007
Loading...

Share This Page