469,573 Members | 1,668 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,573 developers. It's quick & easy.

The void** pointer breaking symmetry?

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 '06 #1
49 2482
el***@cmbi.ru.nl 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.
May 5 '06 #2


el***@cmbi.ru.nl 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.

--
Er*********@sun.com

May 5 '06 #3
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 '06 #4
el***@cmbi.ru.nl 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
May 5 '06 #5
el***@cmbi.ru.nl 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/>
May 5 '06 #6
el***@cmbi.ru.nl 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) ks***@mib.org <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.
May 5 '06 #7
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 '06 #8
el***@cmbi.ru.nl 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.
May 6 '06 #9
el***@cmbi.ru.nl 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
es*****@acm-dot-org.invalid
May 6 '06 #10
Eric Sosman <es*****@acm-dot-org.invalid> writes:
el***@cmbi.ru.nl 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) ks***@mib.org <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.
May 6 '06 #11
> > 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].i=0;
foo[i].d=0;
foo[i].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 '06 #12
el***@cmbi.ru.nl 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].i=0;
foo[i].d=0;
foo[i].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[i]);
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

May 8 '06 #13


el***@cmbi.ru.nl 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].i=0;
foo[i].d=0;
foo[i].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' ...

--
Er*********@sun.com

May 8 '06 #14
> > #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].i=0;
> foo[i].d=0;
> foo[i].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 '06 #15
Eric Sosman wrote:
el***@cmbi.ru.nl 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].i=0;
foo[i].d=0;
foo[i].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 '06 #16
el***@cmbi.ru.nl 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].i=0;
> foo[i].d=0;
> foo[i].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.
May 8 '06 #17
el***@cmbi.ru.nl 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/>
May 8 '06 #18
el***@cmbi.ru.nl 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) ks***@mib.org <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.
May 8 '06 #19
["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].i=0;
foo[i].d=0;
foo[i].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.
May 8 '06 #20
el***@cmbi.ru.nl wrote:
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 [...]


We have a generic pointer type: void *

What if you want to point to one of these generic pointers?
And you are a person who thinks type-safety has advantages
(which is presumably why you chose C instead of Perl for
your program).

You want to have a non-generic pointer that can point to
generic pointers.

After all, if you wanted to use a generic pointer to point to
a generic pointer.... you would just use the generic pointer
you already have.

The designers of C presumably followed this logic, and
decided that void ** should be a pointer to void* (and not
a pointer to anything else).
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].i=0;
foo[i].d=0;
foo[i].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 and trusts in the
laws of physics:
Well, if I were an employer I'd rather have programmer B work for me.

I guess you have never had to port an application from one
platform, to another substantially different one.

In fact I would prefer programmer D who writes:

struct bar
{
int i;
double d;
char *p;
};
static struct bar const bar_defaults = { 0 };

struct bar *p_foo = malloc( FOOS * sizeof *p_foo );
if (!p_foo) do_something.....

for (size_t i = 0; i != FOOS; ++i)
p_foo[i] = bar_defaults;

which avoids the problem of the structure being updated later
and the initialization step forgotten. Also it allows for a field
to have a non-zero default value, something which is
impossible in the inflexible B-brain version.

Further, if the defaults is indeed all bits zero, the compiler
will be likely to generate a memset instruction (or even
call calloc).
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.


Who measures an architecture's efficiency in lines of C code ??

I think you would get on well with Paul Hsieh (another regular
poster here who thinks that not only is catering for non-x86 a
waste of time, but he must also admonish anybody else who
does it).

May 8 '06 #21
el***@cmbi.ru.nl said:
#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
Not having read the entire thread, I don't know what you mean by brain type
A, but I would certainly consider that the calloc is misguided.
and quickly change the wrong code to read

foo=malloc(FOOS*sizeof(*foo));
for (i=0;i<FOOS;i++)
{ foo[i].i=0;
foo[i].d=0;
foo[i].p=NULL; }
Well then, I'm not Brain Type A, because that isn't how I'd change it /at
all/!
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


Weird architectures do not in fact break the laws of physics. They just
break the law of familiarity.

But anyway, I can see from the above verbiage that I'm not Brain Type B
either.

Being, perhaps, of Brain Type C, I prefer to do this in what we like to call
"the right way".

The following assumes you require a deterministically blank collection of
struct FOO - if you know what you want to write into the members of the
collection, which really you ought to before creating the collection in the
first place, then replace blankfoo appropriately:

struct FOO *foo_array_create(size_t n)
{
struct FOO blankfoo = {0};

foo = malloc(n * sizeof *foo); /* note the removal of the stupid constant,
* and the redundant parentheses.
*/
if(foo != NULL) /* note the error checking */
{
for(i = 0; i < n; i++)
{
foo[i] = blankfoo; /* note the non-necessity for knowledge
* about the innards of the struct type */
}
}
return foo;
}

--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 10 '06 #22
Keith Thompson wrote:
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.


Is it suitable and helpful also to understand the following question:

void foo1(const char *p) {}
void foo2(const char **p) {}

int main(void)
{
char *p1;
char **p2;

foo1(p1); /* line 9: consistent type conversion */
foo2(p2); /* line 10: inconsistent type conversion */
}

May 10 '06 #23
lovecreatesbeauty said:
Is it suitable and helpful also to understand the following question:

void foo1(const char *p) {}
void foo2(const char **p) {}

int main(void)
{
char *p1;
char **p2;

foo1(p1); /* line 9: consistent type conversion */
foo2(p2); /* line 10: inconsistent type conversion */
}


It's okay to pass a char * to a function expecting const char *, because all
the function is doing is promising not to write to the object pointed at.

But your second example passes a pointer to pointer to char, to a function
which is expecting a pointer to a const pointer to char. In other words,
the function is asking for a guarantee that the pointer you pass it is a
pointer to a pointer value through which writing is not allowed. This is
not an unreasonable thing to ask - for example, it might use the pointer
value internally in a way that would be corrupted if that pointer value
changed.

Please remember that there is an important difference between const char **p
and char * const * p.

--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 10 '06 #24
Old Wolf wrote:

Well, if I were an employer I'd rather have programmer B work for me.


Should be 'A'.

May 10 '06 #25
Old Wolf said:
el***@cmbi.ru.nl wrote:
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].i=0;
foo[i].d=0;
foo[i].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 and trusts in the
laws of physics:


Well, if I were an employer I'd rather have programmer B work for me.


(You correct this to 'A' in a followup.)

I'd rather employ someone who could get the code right. In my opinion,
neither of the above achieved this.
--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 10 '06 #26

Ben C wrote:
["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].i=0;
foo[i].d=0;
foo[i].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.


YEEEESS! That's my opinion, finally, it's not me against the rest
anymore ;-)
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.


100% acknowledged, looks like another brain type B is around...

Cheers,
Elmar

May 10 '06 #27
Richard Heathfield wrote:

I'd rather employ someone who could get the code right. In my opinion,
neither of the above achieved this.


Well, I agree. But I think A is an improvement on B. And I'd also
guess that programmer 'A' would be more amenable to being taught
the correct way than programmer 'B' would (who, if this guy is
anything to go by, would resist or ignore the advice given).

May 10 '06 #28
> > 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?


If the size computation is wrong, then we got a serious bug and it's OK
if mem_alloc calls the registered rescue&cleanup functions and exits,
no?
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. From my previous experiences, it's always the truly big multi-megabyte allocations that can fail, not the little every-day things. I even
somehow suspect that the OS doesn't use a hard cutoff but says 'OK,
even though you are over the limits, you get another MB. What, you want
another 100MB? Die!'
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...
Well, then the callbacks in mem_alloc would have saved your huge
simulation, assuming that the OS still allowed to open a file, FILE*
also needs space ;-)
I started out as type B and made my way over to something between type
A and Eric's type C.
Between A and C, there's B ;-))
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.


Depends on the size, if it's large and you make it static, you waste
memory. If you put it on the local stack, it may not be portable due to
stack size restrictions ;-(
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.
I would replace 'inadvertent mistakes' with 'restriction to a relevant
subset of the C standard'.
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.
It is all documented in the README, and takes 5 lines:

The following architecture assumptions and programming guidelines were
found to reduce
source code length by 40%, and have thus been choosen as a
prerequisite:
*) The binary representation of 0 or NULL must indeed be 0 for all
datatypes.
*) All pointers must have the same size.
*) Never use side-effects ++/-- on function arguments.
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.
Yep, got that.
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.


While that's true, I am confident that it will never happen. Instead I
think that inefficient architectures with non-zero NULL pointers etc.
will continue to disappear, and one day the C standard will be changed
to avoid that millions of programmers waste millions of manyears on
compatibility with architectures that died dozens of years ago ;-).
Just like C99 is already said to require that integer 0 has all bits at
0 (section 6.2.6.2 of C99, citation without checking myself)

I just googled for architectures with NULL pointers or floating point
0.0 whose binary representation is not 0, but failed. Can anyone
provide examples?

Ciao,
Elmar

May 10 '06 #29
el***@cmbi.ru.nl said:
I just googled for architectures with NULL pointers or floating point
0.0 whose binary representation is not 0, but failed. Can anyone
provide examples?


Please read the FAQ in future to ensure that your question is not answered
there, before asking it here. For your reference:

<http://c-faq.com/null/machexamp.html>

--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 10 '06 #30
Richard Heathfield wrote:
el***@cmbi.ru.nl said:
#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
Not having read the entire thread, I don't know what you mean by brain type
A, but I would certainly consider that the calloc is misguided.
and quickly change the wrong code to read

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


Well then, I'm not Brain Type A, because that isn't how I'd change it /at
all/!
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


Weird architectures do not in fact break the laws of physics. They just
break the law of familiarity.


I didn't say they break the laws of physics. I said the laws of physics
make sure that they never get out of their niche or disappear, that
luckily follows from the first two laws of thermodynamics ;-).

But anyway, I can see from the above verbiage that I'm not Brain Type B
either.

Being, perhaps, of Brain Type C, I prefer to do this in what we like to call
"the right way".

The following assumes you require a deterministically blank collection of
struct FOO - if you know what you want to write into the members of the
collection, which really you ought to before creating the collection in the
first place, then replace blankfoo appropriately:

struct FOO *foo_array_create(size_t n)
{
struct FOO blankfoo = {0};

foo = malloc(n * sizeof *foo); /* note the removal of the stupid constant,
* and the redundant parentheses.
*/
if(foo != NULL) /* note the error checking */
{
for(i = 0; i < n; i++)
{
foo[i] = blankfoo; /* note the non-necessity for knowledge
* about the innards of the struct type */
}
}
return foo;
}


Don't worry, I do not enjoy picking on details like some others here.
So I won't blame you for forgetting the declarations of i and foo.

Anyway, this nice piece of code is certainly an improvement for the
"Type A brain way", since it avoids the danger of forgetting to adapt
the initializer after changes to struct FOO.

On the minus side, you blew the source code to allocate foo by a factor
20 (1 line with calloc, 20=17+2(declaration of i and foo)+1(call to
foo_array_create) in this example.)

Longer source code also tends to translate to longer object code, in
your case the object code is blown by more than a factor of 5, even
with full optimization (GCC 4.1.0, -O3, see below).

And all that as a portability tribute to an architecture I cannot even
find on Google. Is that maybe all an urban legend? Anyway, it raises my
hopes that future versions of the C standard will cut the crap and save
millions of man- and CPU-years. And while they are at it, they can also
introduce 'break x' and 'continue x' to break out of/continue the xth
outer loop, which will save another bunch of millions.

Ciao,
Elmar

0x08048431 <main+17>: movl $0x140,(%esp)
0x08048438 <main+24>: call 0x80482d0 //call to malloc
0x0804843d <main+29>: test %eax,%eax
0x0804843f <main+31>: mov %eax,%ecx
0x08048441 <main+33>: je 0x8048472 <main+82>
0x08048443 <main+35>: xor %edx,%edx
0x08048445 <main+37>: xor %eax,%eax
0x08048447 <main+39>: shl $0x4,%eax
0x0804844a <main+42>: add $0x1,%edx
0x0804844d <main+45>: lea (%ecx,%eax,1),%eax
0x08048450 <main+48>: cmp $0x14,%edx
0x08048453 <main+51>: movl $0x0,0xc(%eax)
0x0804845a <main+58>: movl $0x0,0x4(%eax)
0x08048461 <main+65>: movl $0x0,0x8(%eax)
0x08048468 <main+72>: movl $0x0,(%eax)
0x0804846e <main+78>: mov %edx,%eax
0x08048470 <main+80>: jne 0x8048447 <main+39>

May 10 '06 #31
el***@cmbi.ru.nl said:
Don't worry, I do not enjoy picking on details like some others here.
So I won't blame you for forgetting the declarations of i and foo.
I didn't forget them. I just didn't bother to put them in. This is perfectly
safe, since the code won't compile until they're added.
Anyway, this nice piece of code is certainly an improvement for the
"Type A brain way", since it avoids the danger of forgetting to adapt
the initializer after changes to struct FOO.
Precisely.
On the minus side, you blew the source code to allocate foo by a factor
20 (1 line with calloc, 20=17+2(declaration of i and foo)+1(call to
foo_array_create) in this example.)
I can build a bridge across the brook down the hill from my home, using a
length of rope. It will be astoundingly light.

But I'd rather use the bridge that's already there, which is made out of
steel and wood and concrete. It's thousands of times heavier than the rope,
but I am much more confident about using it without getting wet.
Longer source code also tends to translate to longer object code, in
your case the object code is blown by more than a factor of 5, even
with full optimization (GCC 4.1.0, -O3, see below).
Who cares? Weight is not the only criterion, or even the chief criterion.
And all that as a portability tribute to an architecture I cannot even
find on Google.
Google != Web, Web != Google. Try looking in the obvious places.

Is that maybe all an urban legend?


Yeah, right. You /have/ read the FAQ, have you not?
--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 10 '06 #32
Old Wolf wrote:
el***@cmbi.ru.nl wrote:
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...
We have a generic pointer type: void *

What if you want to point to one of these generic pointers?
And you are a person who thinks type-safety has advantages
(which is presumably why you chose C instead of Perl for
your program).

You want to have a non-generic pointer that can point to
generic pointers.

After all, if you wanted to use a generic pointer to point to
a generic pointer.... you would just use the generic pointer
you already have.

The designers of C presumably followed this logic, and
decided that void ** should be a pointer to void* (and not
a pointer to anything else).
I think you zapped in a bit later. As someone pointed out earlier, the
term 'generic' is maybe causing more confusion than it helps. I also
didn't mumble about fundamental changes to the C language (I'm not Don
Quichote), but about a little GCC compiler flag to allow implicit type
casts from and to void** on architectures where it's safe (because all
pointers are guaranteed to have the same size).

The reason is that a lot of code cluttering is currently caused by the
inability to pass a pointer to any pointer to a function, e.g.

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

which should free a pointer and set it to NULL can currently not be
called without violent explicit casts that destroy type-safety. With
the suggestions made above, the compiler would allow passing arguments
of type char**, int**, also char***, int***, but not
char*,int*,char,int to the function.
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].i=0;
foo[i].d=0;
foo[i].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 and trusts in the
laws of physics:
Well, if I were an employer I'd rather have programmer B work for me.


I know, you changed to 'A' in the mean time.
Think twice: Type B was defined as someone knowing which parts of the C
standard are leftovers from (ancient?) inefficient architectures which
are extremely unlike to be of any use in the future, but neverthless
blow the source code by ~40%. I assume this knowledge implies that Type
B can switch to Type A if asked for. So as the employer you got the
choice: Full portability to your vacuum cleaner&refridgerator, or 40%
more output for the same salary.
I guess you have never had to port an application from one
platform, to another substantially different one.
Not so substantially different that the binary representation of a NULL
pointer was not 0. But I guess neither had you ...? ;-)

In fact I would prefer programmer D who writes:

struct bar
{
int i;
double d;
char *p;
};
static struct bar const bar_defaults = { 0 };

struct bar *p_foo = malloc( FOOS * sizeof *p_foo );
if (!p_foo) do_something.....

for (size_t i = 0; i != FOOS; ++i)
p_foo[i] = bar_defaults;

which avoids the problem of the structure being updated later
and the initialization step forgotten. Also it allows for a field
to have a non-zero default value, something which is
impossible in the inflexible B-brain version.
Yep, you are on the same smart track as Richard.
Further, if the defaults is indeed all bits zero, the compiler
will be likely to generate a memset instruction (or even
call calloc).
Unless you got secret alien (Intel?) technology, I fear that's a myth.
Compared to calloc, you managed to blow the object code by a factor of
~15 (fifteen!) (GCC 4.1.0 with -O3, attached at the end).
Even though you declared bar_defaults as const, GCC doesn't believe you
and insists on copying the content, so no chance for memset (or even
calloc) ;-((
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.
Who measures an architecture's efficiency in lines of C code ??


Everyone? ;-) Seriously, people programming Python/Perl always say: 'C,
that's merely an Assembler with portable syntax'. And indeed, the
length of C source code is strongly related to the number of Assembler
instructions generated in the end (as your example demonstrated
non-voluntarily ;-), and the number of Assembler instructions needed to
reach a goal is related to an architectures efficiency. (To avoid a
RISC/CISC debate, feel free to replace 'Assembler instructions' with
'microOPs'.)

I think you would get on well with Paul Hsieh (another regular
poster here who thinks that not only is catering for non-x86 a
waste of time, but he must also admonish anybody else who
does it).


That's not really my opinion. There are for example mainstream
architectures with different endianness, which prohibits certain
shortcuts. And that's not suprising, since I think that little endian
is only marginally more efficient.

Ciao,
Elmar

0x080483d3 <main+19>: movl $0xa0,(%esp)
0x080483da <main+26>: call 0x80482d0 // Call to malloc
0x080483df <main+31>: mov 0x8048560,%ecx // Get copy of
bar_defaults
0x080483e5 <main+37>: mov 0x8048558,%esi
0x080483eb <main+43>: mov 0x8048564,%edx
0x080483f1 <main+49>: mov 0x804855c,%ebx
0x080483f7 <main+55>: mov %esi,(%eax) // Unrolled storage loop
0x080483f9 <main+57>: mov %ebx,0x4(%eax)
0x080483fc <main+60>: mov %ecx,0x8(%eax)
0x080483ff <main+63>: mov %esi,0x10(%eax)
0x08048402 <main+66>: mov %ebx,0x14(%eax)
0x08048405 <main+69>: mov %ecx,0x18(%eax)
0x08048408 <main+72>: mov %esi,0x20(%eax)
0x0804840b <main+75>: mov %ebx,0x24(%eax)
0x0804840e <main+78>: mov %ecx,0x28(%eax)
0x08048411 <main+81>: mov %esi,0x30(%eax)
0x08048414 <main+84>: mov %ebx,0x34(%eax)
0x08048417 <main+87>: mov %ecx,0x38(%eax)
0x0804841a <main+90>: mov %esi,0x40(%eax)
0x0804841d <main+93>: mov %ebx,0x44(%eax)
0x08048420 <main+96>: mov %ecx,0x48(%eax)
0x08048423 <main+99>: mov %esi,0x50(%eax)
0x08048426 <main+102>: mov %ebx,0x54(%eax)
0x08048429 <main+105>: mov %ecx,0x58(%eax)
0x0804842c <main+108>: mov %esi,0x60(%eax)
0x0804842f <main+111>: mov %ebx,0x64(%eax)
0x08048432 <main+114>: mov %ecx,0x68(%eax)
0x08048435 <main+117>: mov %esi,0x70(%eax)
0x08048438 <main+120>: mov %edx,0xc(%eax)
0x0804843b <main+123>: mov %edx,0x1c(%eax)
0x0804843e <main+126>: mov %edx,0x2c(%eax)
0x08048441 <main+129>: mov %edx,0x3c(%eax)
0x08048444 <main+132>: mov %edx,0x4c(%eax)
0x08048447 <main+135>: mov %edx,0x5c(%eax)
0x0804844a <main+138>: mov %edx,0x6c(%eax)
0x0804844d <main+141>: mov %ebx,0x74(%eax)
0x08048450 <main+144>: mov %ecx,0x78(%eax)
0x08048453 <main+147>: mov %esi,0x80(%eax)
0x08048459 <main+153>: mov %ebx,0x84(%eax)
0x0804845f <main+159>: mov %ecx,0x88(%eax)
0x08048465 <main+165>: mov %esi,0x90(%eax)
0x0804846b <main+171>: mov %ebx,0x94(%eax)
0x08048471 <main+177>: mov %ecx,0x98(%eax)
0x08048477 <main+183>: mov %edx,0x7c(%eax)
0x0804847a <main+186>: mov %edx,0x8c(%eax)
0x08048480 <main+192>: mov %edx,0x9c(%eax)

May 10 '06 #33
On Tue, 09 May 2006 05:51:10 +0000,
Richard Heathfield <in*****@invalid.invalid> wrote
in Msg. <xI********************@bt.com>
struct FOO blankfoo = {0};
[...]
foo[i] = blankfoo;


Funny! I never realized that the assignment operator works with structs.
Just never thought of it. I would have used memcpy in this line (as in
fact I have in the past. A bit of code cleanup is in order now).

robert
May 10 '06 #34
[The NULL pointer not being 0...]
Is that maybe all an urban legend?


Yeah, right. You /have/ read the FAQ, have you not?


Yep, that was part of my hypothesis:
Four examples are given:
1) Prime: "..the demise of Prime Computer wasn't shocking"
(http://www.davidmandel.com/personal/prime.html)
"Later models changed the NULL pointer from Segment 07777, Offset 0 to
Segment 0, Offset 0", seems like they learned the lesson ;-)
2) Honeywell-Bull: Most links point into a computer museum, seems like
they released the last non Intel/PowerPC based machine more than 20
years ago.
3) CDC: "The CDC Cyber range of mainframe/super-computers were Control
Data Corporation (CDC)'s primary products during the 1970's and 1980's"
(Wikipedia).
4) Symbolics Lisp Machines: "By 1995, the Lisp machine era had ended,
and with it Symbolics' hopes for success."

In short: checking if a pointer is NULL is one of the most frequent
pointer-related tasks. If NULL is actually 0, that compiles to
something equivalent to "test eax,eax", otherwise it ends up as "cmp
eax,SOME_LONG_BITPATTERN", which gives about 5 (32bit) to 7 (64bit)
times the code size. This is the evolutionary pressure that makes the
NULL choice non-arbitrary, contrary to what is claimed in the FAQ.

I think everything is said: you want to be portable to machines that
disappeared 20 years ago, and I am sorry for all the millions of
man-years wasted on NULL not being 0 (including this fruitless
discussion which keeps us from doing something useful). Hopefully, the
C standard committee will one day put an end to it...

I now go on holiday,
ciao,
Elmar

May 10 '06 #35
el***@cmbi.ru.nl said:
[The NULL pointer not being 0...]
> Is that maybe all an urban legend?


Yeah, right. You /have/ read the FAQ, have you not?


Yep, that was part of my hypothesis:
Four examples are given:
1) Prime: "..the demise of Prime Computer wasn't shocking"
(http://www.davidmandel.com/personal/prime.html)
"Later models changed the NULL pointer from Segment 07777, Offset 0 to
Segment 0, Offset 0", seems like they learned the lesson ;-)
2) Honeywell-Bull: Most links point into a computer museum, seems like
they released the last non Intel/PowerPC based machine more than 20
years ago.
3) CDC: "The CDC Cyber range of mainframe/super-computers were Control
Data Corporation (CDC)'s primary products during the 1970's and 1980's"
(Wikipedia).
4) Symbolics Lisp Machines: "By 1995, the Lisp machine era had ended,
and with it Symbolics' hopes for success."


You appear to have missed the point. Such machines can exist. We know this
because they have existed in the past. Indeed, it would be surprising if at
least some of them were not in service even now. And similar machines may
exist again in the future.

The question is: is this a sufficiently important concern for you to keep
your code portable to such architectures, or aren't you fussed? If you're
not fussed, great, fine, whatever. But for those who /are/ fussed, this
newsgroup is where such people will find assistance when they need it.

--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 10 '06 #36
el***@cmbi.ru.nl wrote:
In short: checking if a pointer is NULL is one of the most frequent
pointer-related tasks. If NULL is actually 0, that compiles to
something equivalent to "test eax,eax", otherwise it ends up as "cmp
eax,SOME_LONG_BITPATTERN", which gives about 5 (32bit) to 7 (64bit)
times the code size.


On the only architecture you are aware of.

Richard
May 10 '06 #37
On 2006-05-10, el***@cmbi.ru.nl <el***@cmbi.ru.nl> wrote:
In short: checking if a pointer is NULL is one of the most frequent
pointer-related tasks. If NULL is actually 0, that compiles to
something equivalent to "test eax,eax", otherwise it ends up as "cmp
eax,SOME_LONG_BITPATTERN", which gives about 5 (32bit) to 7 (64bit)
times the code size. This is the evolutionary pressure that makes the
NULL choice non-arbitrary, contrary to what is claimed in the FAQ.


why not

subcc r1,0
jpn ... /* jump if pointer is null */

or something more bizarre
May 10 '06 #38
el***@cmbi.ru.nl writes:
[...]
From my previous experiences, it's always the truly big multi-megabyte
allocations that can fail, not the little every-day things. I even
somehow suspect that the OS doesn't use a hard cutoff but says 'OK,
even though you are over the limits, you get another MB. What, you want
another 100MB? Die!'


Then your experience is incomplete.

If most of the memory you allocate is in large chunks, most of your
allocation failures will be on attempts to allocate large chunks.

How many "small" chunks of memory do you think the system is going to
let you allocate? If the answer is anything less than "infinitely
many", you'd better check the result of each malloc(). It's always
possible that a small allocation will fail because you just did a
(successful) large allocation that gobbled up almost all of the
remaining free space.

If you want to write software that works most of the time, you can be
selective about which allocations you check. Personally, I consider
that a waste of time; I'd much rather write software that just works
(where "works" in this case means either working properly or failing
gracefully if it runs out of resources).

Of course, if you check the result of malloc() by wrapping it in
another function, that's perfectly fine.
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...


Well, then the callbacks in mem_alloc would have saved your huge
simulation, assuming that the OS still allowed to open a file, FILE*
also needs space ;-)


So you *are* checking whether your allocations succeeded. What's the
point of claiming that small allocations can never fail?

--
Keith Thompson (The_Other_Keith) ks***@mib.org <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.
May 10 '06 #39
el***@cmbi.ru.nl writes:
[...]
And all that as a portability tribute to an architecture I cannot even
find on Google. Is that maybe all an urban legend? Anyway, it raises my
hopes that future versions of the C standard will cut the crap and save
millions of man- and CPU-years. And while they are at it, they can also
introduce 'break x' and 'continue x' to break out of/continue the xth
outer loop, which will save another bunch of millions.


Using "break 3;" to break out of the 3rd enclosing loop would be a
truly horrible idea, leading to unmaintainable code and bugs that are
nearly impossible to track down.

A labelled break, that breaks out of a loop specified *by name*, would
be a great idea, one that I've advocated before.

Given C's single-level break and continue statements, though, there is
an easy workaround: the goto statement. I'm certainly not a fan of it
(yes, I've read Dijstra's essay), but a multi-level break in a
language that doesn't directly support it is one of the few cases
where it's appropriate.

--
Keith Thompson (The_Other_Keith) ks***@mib.org <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.
May 10 '06 #40
el***@cmbi.ru.nl writes:
[...]
In short: checking if a pointer is NULL is one of the most frequent
pointer-related tasks. If NULL is actually 0, that compiles to
something equivalent to "test eax,eax", otherwise it ends up as "cmp
eax,SOME_LONG_BITPATTERN", which gives about 5 (32bit) to 7 (64bit)
times the code size. This is the evolutionary pressure that makes the
NULL choice non-arbitrary, contrary to what is claimed in the FAQ.


Surely a machine on which the most natural representation for a null
pointer is something other than all-bits-zero will have a simple way
to compare a pointer against that value. Perhaps even a dedicated
test_null_pointer instruction.

--
Keith Thompson (The_Other_Keith) ks***@mib.org <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.
May 10 '06 #41
Keith Thompson wrote:
.... snip ...
A labelled break, that breaks out of a loop specified *by name*,
would be a great idea, one that I've advocated before.


We've got one. It's called a 'goto'. Works quite nicely.

--
"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/>
May 11 '06 #42
CBFalconer <cb********@yahoo.com> writes:
Keith Thompson wrote:

... snip ...

A labelled break, that breaks out of a loop specified *by name*,
would be a great idea, one that I've advocated before.


We've got one. It's called a 'goto'. Works quite nicely.


Yes, as I mentioned in a paragraph that you snipped.

The problem is that it works *too* well. I still want an explicit
labelled break; I've found it very useful in languages that support
it.

Any arguments that goto is just as good as a labelled break apply
equally well to the existing single-level break (except for the very
real and valid argument that single-level break already exists in the
language). (And yes, this is arguably off-topic.)

--
Keith Thompson (The_Other_Keith) ks***@mib.org <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.
May 11 '06 #43
Keith Thompson wrote:
el***@cmbi.ru.nl writes:
[...]
In short: checking if a pointer is NULL is one of the most frequent
pointer-related tasks. If NULL is actually 0, that compiles to
something equivalent to "test eax,eax", otherwise it ends up as "cmp
eax,SOME_LONG_BITPATTERN", which gives about 5 (32bit) to 7 (64bit)
times the code size. This is the evolutionary pressure that makes the
NULL choice non-arbitrary, contrary to what is claimed in the FAQ.


Surely a machine on which the most natural representation for a null
pointer is something other than all-bits-zero will have a simple way
to compare a pointer against that value. Perhaps even a dedicated
test_null_pointer instruction.


I thought about that, including the additional silicon required for a
dedicated instruction, as well as an additional set_pointer_to_null
instruction etc.

Funnily however, here's the complete FAQ piece for the Prime 50:

"The Prime 50 series used segment 07777, offset 0 for the null pointer,
at least for PL/I. Later models used segment 0, offset 0 for null
pointers in C, necessitating new instructions such as TCNP (Test C Null
Pointer), evidently as a sop to all the extant poorly-written C code
which made incorrect assumptions."

(I'm not sure about the logic behind this statement, i.e. what TCNP
actually did (maybe test against 0:0 and 07777:0 in one shot?), but it
shows the point: being forced to finish their work quickly, people take
shortcuts where possible (some more, some less justified ;-). They
generate billions of lines of codes, and if *afterwards* someone
designs a new architecture, he'll think twice before breaking a
significant portion of the existing code base, e.g. by messing with
NULL or introducing pointers of different size. And he'll certainly
never make an arbitrary choice like 07777 again. Richard said that it
happened in the past, so it can happen again. I think the fact that the
only examples in the FAQ are >20 years old and from the early days of C
either indicates that the FAQ is now equally old - or gives a hint that
in the mean time, the pressure of the existing code base has grown so
large that noone ever dared to do it again...

Greetings,
Elmar

May 11 '06 #44
el***@cmbi.ru.nl said:
They
generate billions of lines of codes, and if *afterwards* someone
designs a new architecture, he'll think twice before breaking a
significant portion of the existing code base, e.g. by messing with
NULL or introducing pointers of different size.


[Some Microsoft application groups] "...thought they had good reason for
thinking they could safely use +2 instead of +sizeof(int). Microsoft writes
its own compilers, and that gave programmers a false sense of security. As
one programmer put it a couple of years ago, 'The compiler group would
never change something that would break all of our code.' That programmer
was wrong. The compiler group changed the size of ints (and a number of
other things) to generate faster and smaller code for Intel's 80386 and
newer processors. The compiler group didn't want to break internal code,
but it was far more important for them to remain competitive in the
marketplace. After all, it wasn't their fault that some Microsoft
programmers made erroneous assumptions." - Steve Maguire, former Microsoft
programmer.

--
Richard Heathfield
"Usenet is a strange place" - dmr 29/7/1999
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 11 '06 #45
Richard Heathfield wrote:
el***@cmbi.ru.nl said:
They
generate billions of lines of codes, and if *afterwards* someone
designs a new architecture, he'll think twice before breaking a
significant portion of the existing code base, e.g. by messing with
NULL or introducing pointers of different size.


[Some Microsoft application groups] "...thought they had good reason for
thinking they could safely use +2 instead of +sizeof(int). Microsoft writes
its own compilers, and that gave programmers a false sense of security. As
one programmer put it a couple of years ago, 'The compiler group would
never change something that would break all of our code.' That programmer
was wrong. The compiler group changed the size of ints (and a number of
other things) to generate faster and smaller code for Intel's 80386 and
newer processors. The compiler group didn't want to break internal code,
but it was far more important for them to remain competitive in the
marketplace. After all, it wasn't their fault that some Microsoft
programmers made erroneous assumptions." - Steve Maguire, former Microsoft
programmer.


A real instance of something from years ago coming back in to vogue and
breaking code. Once upon a time, back int he days of the 8086, pointers
were not always the same size as int, sometimes they were larger, so you
could not store a pointer in an int and manipulate it. Years passed and
"all" systems did the obvious thing of making pointers and ints the same
size. People wrote code that assumed this. More years have passed and
now we have 64 bit systems where a pointer can be larger than an int
thus breaking code that assumes it can store a pointer in an int.
--
Flash Gordon, living in interesting times.
Web site - http://home.flash-gordon.me.uk/
comp.lang.c posting guidelines and intro:
http://clc-wiki.net/wiki/Intro_to_clc
May 11 '06 #46
Flash Gordon <sp**@flash-gordon.me.uk> wrote:
Richard Heathfield wrote:
el***@cmbi.ru.nl said:
They
generate billions of lines of codes, and if *afterwards* someone
designs a new architecture, he'll think twice before breaking a
significant portion of the existing code base, e.g. by messing with
NULL or introducing pointers of different size.


[Some Microsoft application groups] "...thought they had good reason for
thinking they could safely use +2 instead of +sizeof(int). Microsoft writes
its own compilers, and that gave programmers a false sense of security. As
one programmer put it a couple of years ago, 'The compiler group would
never change something that would break all of our code.' That programmer
was wrong. The compiler group changed the size of ints (and a number of
other things) to generate faster and smaller code for Intel's 80386 and
newer processors. The compiler group didn't want to break internal code,
but it was far more important for them to remain competitive in the
marketplace. After all, it wasn't their fault that some Microsoft
programmers made erroneous assumptions." - Steve Maguire, former Microsoft
programmer.


A real instance of something from years ago coming back in to vogue and
breaking code. Once upon a time, back int he days of the 8086, pointers
were not always the same size as int, sometimes they were larger, so you
could not store a pointer in an int and manipulate it. Years passed and
"all" systems did the obvious thing of making pointers and ints the same
size. People wrote code that assumed this. More years have passed and
now we have 64 bit systems where a pointer can be larger than an int
thus breaking code that assumes it can store a pointer in an int.


Cue also the lParam/wParam debacle.

Richard
May 11 '06 #47
Keith Thompson wrote:
CBFalconer <cb********@yahoo.com> writes:
Keith Thompson wrote:

... snip ...

A labelled break, that breaks out of a loop specified *by name*,
would be a great idea, one that I've advocated before.


We've got one. It's called a 'goto'. Works quite nicely.


Yes, as I mentioned in a paragraph that you snipped.

The problem is that it works *too* well. I still want an explicit
labelled break; I've found it very useful in languages that support
it.

Any arguments that goto is just as good as a labelled break apply
equally well to the existing single-level break (except for the very
real and valid argument that single-level break already exists in the
language). (And yes, this is arguably off-topic.)


No, I think it is a matter of style.

I agree with your points, and disagree with your conclusions. I
would be willing to dispense with break entirely, were it not for
the case anamoly. continue I find useful to mark empty statements,
as in:

while (whatever) continue;

But I won't give up goto.

--
Some informative links:
news:news.announce.newusers
http://www.geocities.com/nnqweb/
http://www.catb.org/~esr/faqs/smart-questions.html
http://www.caliburn.nl/topposting.html
http://www.netmeister.org/news/learn2quote.html
May 11 '06 #48
CBFalconer said:

But I won't give up goto.


Remember the steps, mate!

--
Richard Heathfield
You kinda have to have seen "Finding Nemo" to get this...
http://www.cpax.org.uk
email: rjh at above domain (but drop the www, obviously)
May 11 '06 #49
On Sat, 06 May 2006 13:21:21 -0400, Eric Sosman
<es*****@acm-dot-org.invalid> wrote:
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.


if i know the *size* i can set the number to zero
May 15 '06 #50

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

23 posts views Thread by Ian Tuomi | last post: by
14 posts views Thread by Enrico `Trippo' Porreca | last post: by
188 posts views Thread by infobahn | last post: by
14 posts views Thread by arun | last post: by
28 posts views Thread by Peter Olcott | last post: by
160 posts views Thread by raphfrk | last post: by
reply views Thread by suresh191 | last post: by
4 posts views Thread by guiromero | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.