The void** pointer breaking symmetry?

O

Old Wolf

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=0;
foo.d=0;
foo.p=NULL; }

And then, there's brain type B who sees that the 'correct' version
takes 5 lines instead of 1, reduces readability, maintainability, can be
a source of additional errors and wastes developer life time.
Type B simply uses the 'elegant' calloc approach 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 = 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).
 
R

Richard Heathfield

(e-mail address removed) 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=0;
foo.d=0;
foo.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 = blankfoo; /* note the non-necessity for knowledge
* about the innards of the struct type */
}
}
return foo;
}
 
L

lovecreatesbeauty

Keith said:
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 */
}
 
R

Richard Heathfield

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.
 
R

Richard Heathfield

Old Wolf said:
there are two types of programming brains:

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

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

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

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

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.
 
E

elmar

Ben said:
["Brain Type B":]
#define FOOS 10
struct
{ int i;
double d;
char *p; } *foo;

foo=calloc(FOOS,sizeof(*foo));
Looking at the piece of code above, brain type A will cry in pain
and quickly change the wrong code to read

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

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

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


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
 
O

Old Wolf

Richard said:
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).
 
E

elmar

Indeed, I am not used to the 'brain type A' way of thinking where it's
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 ;-))
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
 
R

Richard Heathfield

(e-mail address removed) 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>
 
E

elmar

Richard said:
(e-mail address removed) 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=0;
foo.d=0;
foo.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 = 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>
 
R

Richard Heathfield

(e-mail address removed) 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?
 
E

elmar

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=0;
foo.d=0;
foo.p=NULL; }

And then, there's brain type B who sees that the 'correct' version
takes 5 lines instead of 1, reduces readability, maintainability, can be
a source of additional errors and wastes developer life time.
Type B simply uses the 'elegant' calloc approach 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 = 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) ;-((
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)
 
R

Robert Latest

On Tue, 09 May 2006 05:51:10 +0000,
in Msg. said:
struct FOO blankfoo = {0};
[...]

foo = 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
 
E

elmar

[The NULL pointer not being 0...]
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
 
R

Richard Heathfield

(e-mail address removed) said:
[The NULL pointer not being 0...]
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.
 
R

Richard Bos

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
 
J

Jordan Abel

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
 
K

Keith Thompson

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.
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?
 
K

Keith Thompson

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.
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,050
Latest member
AngelS122

Latest Threads

Top