brain teasers

U

user923005

user923005 said:
Or even in modern C:
void swap( void *a, void *b, size_t size )
{
char temp[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}

Please don't quote signatures.
Certainly, the most elegant C version (due to C99 semantics).

[...]

For certain values of "elegant".

If there isn't enough space to allocate the temporary buffer, this
version invokes undefined behavior. In an alternative version using
malloc(), you can catch an allocation error and fall back to an
alternative technique.

If someone uses malloc() in an exchange function, they need to be
slapped[0], unless you are exchanging 5 GB x-ray images or some other
absurdity [in which case you would probably use a customized exchange
function just for that purpose].

Having an exchange function fail because not enough auto memory is
available is not more probable than having the C program fail in some
other function because not enough automatic storage is available.
Hence, if the technique is dangerous here, then C programs are
dangerous in general. The dreadful, dreary slowness of malloc() makes
this notion so unpalatable that the core dump is almost pleasant[1] by
contrast.

[0] I can't figure out what I did with my poleaxe machine, so a slap
will have to do.
[1] But if Nudds flys (flies?) out of my left nostril {rather than a
core dump}, I will have to concede the issue.
 
K

Keith Thompson

user923005 said:
user923005 said:
Or even in modern C:
void swap( void *a, void *b, size_t size )
{
char temp[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
[...]
For certain values of "elegant".

If there isn't enough space to allocate the temporary buffer, this
version invokes undefined behavior. In an alternative version using
malloc(), you can catch an allocation error and fall back to an
alternative technique.

If someone uses malloc() in an exchange function, they need to be
slapped[0], unless you are exchanging 5 GB x-ray images or some other
absurdity [in which case you would probably use a customized exchange
function just for that purpose].

Having an exchange function fail because not enough auto memory is
available is not more probable than having the C program fail in some
other function because not enough automatic storage is available.
Hence, if the technique is dangerous here, then C programs are
dangerous in general. The dreadful, dreary slowness of malloc() makes
this notion so unpalatable that the core dump is almost pleasant[1] by
contrast.

Using a VLA and risking undefined behavior if you run out of memory
(you could be swapping very large objects) isn't what I'd call
elegant. You could, I suppose, restrict the size of the objects beng
swapped.

If you want to use a temporary that's the full size of the objects
being swapped, malloc() is the only safe way to go. If malloc() isn't
acceptable, just swap one byte at a time.
 
U

user923005

user923005 said:
[...]
Or even in modern C:
void swap( void *a, void *b, size_t size )
{
char temp[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
} [...]
For certain values of "elegant".
If there isn't enough space to allocate the temporary buffer, this
version invokes undefined behavior. In an alternative version using
malloc(), you can catch an allocation error and fall back to an
alternative technique.
If someone uses malloc() in an exchange function, they need to be
slapped[0], unless you are exchanging 5 GB x-ray images or some other
absurdity [in which case you would probably use a customized exchange
function just for that purpose].
Having an exchange function fail because not enough auto memory is
available is not more probable than having the C program fail in some
other function because not enough automatic storage is available.
Hence, if the technique is dangerous here, then C programs are
dangerous in general. The dreadful, dreary slowness of malloc() makes
this notion so unpalatable that the core dump is almost pleasant[1] by
contrast.

Using a VLA and risking undefined behavior if you run out of memory
(you could be swapping very large objects) isn't what I'd call
elegant. You could, I suppose, restrict the size of the objects beng
swapped.

Using a VLA is no more dangerous than using an object of the same size
as the VLA.
If we are using the function generically, that indicates that
somewhere in the program we are eventually going to refer to actual
objects to feed to our exchange function. How do we tend to use
exchange functions? Often (for instance) to sort things in a vector
or to reform a heap or to reorder a tree or something of that nature.
An exchange function is mathematically useless, unless there is some
kind of order implied. Ordering implies a collection of objects (e.g.
if there are only two of them, then actually exchanging them is a
waste of time). If the addition of a single temporary to automatic
memory is unsafe, then the program itself is unsafe. Certainly, we
are going to be manipulating collections of these things elsewhere in
the program.
If you want to use a temporary that's the full size of the objects
being swapped, malloc() is the only safe way to go. If malloc() isn't
acceptable, just swap one byte at a time.

Or do something clever like the Bentley / McIlroy sort I posted (which
tends to be a lot faster than swapping one byte at a time).

On the other hand, perhaps it really means that the C++ template is
even more superior than I ever imagined.

The reason I am kicking up such a fuss on this subject is that this
(exchange/compare sorts of operations) is the kind of thing that will
have a severe impact on performance (even on a vector in the ideal
case of selection sort which minimizes exchanges we will still have
exchanges proportional at least to N). Writing a bad exchange
function (and I consider an exchange function that calls malloc() and
free() a very, very bad one) will definitely have a very severe effect
on overall performance.
 
U

user923005

user923005 said:
[...]
Or even in modern C:
void swap( void *a, void *b, size_t size )
{
char temp[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
} [...]
For certain values of "elegant".
If there isn't enough space to allocate the temporary buffer, this
version invokes undefined behavior. In an alternative version using
malloc(), you can catch an allocation error and fall back to an
alternative technique.
If someone uses malloc() in an exchange function, they need to be
slapped[0], unless you are exchanging 5 GB x-ray images or some other
absurdity [in which case you would probably use a customized exchange
function just for that purpose].
Having an exchange function fail because not enough auto memory is
available is not more probable than having the C program fail in some
other function because not enough automatic storage is available.
Hence, if the technique is dangerous here, then C programs are
dangerous in general. The dreadful, dreary slowness of malloc() makes
this notion so unpalatable that the core dump is almost pleasant[1] by
contrast.
Using a VLA and risking undefined behavior if you run out of memory
(you could be swapping very large objects) isn't what I'd call
elegant. You could, I suppose, restrict the size of the objects beng
swapped.

Using a VLA is no more dangerous than using an object of the same size
as the VLA.
If we are using the function generically, that indicates that
somewhere in the program we are eventually going to refer to actual
objects to feed to our exchange function. How do we tend to use
exchange functions? Often (for instance) to sort things in a vector
or to reform a heap or to reorder a tree or something of that nature.
An exchange function is mathematically useless, unless there is some
kind of order implied. Ordering implies a collection of objects (e.g.
if there are only two of them, then actually exchanging them is a
waste of time). If the addition of a single temporary to automatic
memory is unsafe, then the program itself is unsafe. Certainly, we
are going to be manipulating collections of these things elsewhere in
the program.
If you want to use a temporary that's the full size of the objects
being swapped, malloc() is the only safe way to go. If malloc() isn't
acceptable, just swap one byte at a time.

Or do something clever like the Bentley / McIlroy sort I posted (which
tends to be a lot faster than swapping one byte at a time).

On the other hand, perhaps it really means that the C++ template is
even more superior than I ever imagined.

The reason I am kicking up such a fuss on this subject is that this
(exchange/compare sorts of operations) is the kind of thing that will
have a severe impact on performance (even on a vector in the ideal
case of selection sort which minimizes exchanges we will still have
exchanges proportional at least to N). Writing a bad exchange
function (and I consider an exchange function that calls malloc() and
free() a very, very bad one) will definitely have a very severe effect
on overall performance.

Having thought about this a bit, I think the superiority of the C++
template just got a huge hole shot in its foot, unless we make the
exchange a class member that stores a temporary, or unless we pass the
temporary in as a parameter. After all, the template exchange method
(if the temp is not part of the class) is going to do a new/delete
when we create the temp to perform the exchange. This notion is of
such dramatic importance that I feel a burning need to do a code
review of a few hundred thousand lines (obviously, I can't do this in
a day!).
 
I

Ian Collins

user923005 said:
Having thought about this a bit, I think the superiority of the C++
template just got a huge hole shot in its foot, unless we make the
exchange a class member that stores a temporary, or unless we pass the
temporary in as a parameter. After all, the template exchange method
(if the temp is not part of the class) is going to do a new/delete
when we create the temp to perform the exchange.
OK, I've kept out of this so far, but that assumption is wrong. The
temporary is just another automatic variable. Thus the C++ template
solution suffers the same potential stack exhaustion problems as my VLA
suggestion. On the other hand, not having to pass things via void*
gives the compiler a better shot at optimising the copies.

The same applies to sorting in general, this is one area where C++
(through aggressive optimisation) can outperform C where templates
rather than void* are used to provide a generic solution.
 
U

user923005

OK, I've kept out of this so far, but that assumption is wrong. The
temporary is just another automatic variable. Thus the C++ template
solution suffers the same potential stack exhaustion problems as my VLA
suggestion.

When you create a temporary, it will call the constructor. Typically,
the constructor will call new. You are right in the case of
fundamental types like integers and doubles.
On the other hand, not having to pass things via void*
gives the compiler a better shot at optimising the copies.

The same applies to sorting in general, this is one area where C++
(through aggressive optimisation) can outperform C where templates
rather than void* are used to provide a generic solution.

It turns out I am full of crap anyway. I was going on old, old
profile runs where malloc()/free() were time pigs of the worst kind.
I just did a benchmark and my old profiles simply do not hold up:

dcorbit@DCORBIT64 /c/tmp
$ gcc -W -Wall -ansi -pedantic -DUNIT_TEST threesorts.c
threesorts.c: In function 'autoswap':
threesorts.c:18: warning: ISO C90 forbids variable-size array 'temp'

dcorbit@DCORBIT64 /c/tmp
$ ./a
auto heap sort took 28.407000 seconds
heap heap sort took 29.392000 seconds
etype heap sort took 27.595000 seconds

dcorbit@DCORBIT64 /c/tmp

$ cat threesorts.c
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef double Etype;

void etypeswap(Etype *a, Etype *b)
{
Etype temp;
temp = *a;
*a = *b;
*b = temp;
}

void autoswap(void *a, void *b, size_t size)
{
char temp[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}

void heapswap(void *a, void *b, size_t size)
{
char *temp = malloc(size);
assert(temp);
if (temp) {
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
free(temp);
} else {
printf("Memory allocation error in %s:%s at %u\n", __FILE__,
__FUNCTION__, (unsigned) __LINE__);
}
}

/* This version of heapsort is adapted from:
*
* Data Structures and Algorithm Analysis in C (Second Edition) by
Mark Allen
* Weiss Addison-Wesley, 1997 ISBN: 0-201-49840-5 Page 228.
*
* It is simple and interesting, but quick-sort is better than heap-
sort. */

#define LeftChild( i ) ( 2 * ( i ) + 1 )
int GT(Etype a, Etype b)
{
return (a > b);
}
int LT(Etype a, Etype b)
{
return (a < b);
}
void perc_down(Etype A[], size_t i, size_t N)
{
size_t Child;
Etype Tmp;

for (Tmp = A; LeftChild(i) < N; i = Child) {
Child = LeftChild(i);
if (Child != N - 1 && GT(A[Child + 1], A[Child]))
Child++;
if (LT(Tmp, A[Child]))
A = A[Child];
else
break;
}
A = Tmp;
}

void autoheapsort(Etype A[], size_t N)
{
int i;

for (i = N / 2; i >= 0; i--)
/* BuildHeap */
perc_down(A, i, N);
for (i = N - 1; i > 0; i--) {
autoswap(&A[0], &A, sizeof(Etype));
/* DeleteMax */
perc_down(A, 0, i);
}
}

void heapheapsort(Etype A[], size_t N)
{
long i;

for (i = N / 2; i >= 0; i--)
/* BuildHeap */
perc_down(A, i, N);
for (i = N - 1; i > 0; i--) {
heapswap(&A[0], &A, sizeof(Etype));
/* DeleteMax */
perc_down(A, 0, i);
}
}

void etypeheapsort(Etype A[], size_t N)
{
long i;

for (i = N / 2; i >= 0; i--)
/* BuildHeap */
perc_down(A, i, N);
for (i = N - 1; i > 0; i--) {
etypeswap(&A[0], &A);
/* DeleteMax */
perc_down(A, 0, i);
}
}

#ifdef UNIT_TEST

#include <time.h>
#include <math.h>
Etype array[10000000];
Etype tarray[10000000];
Etype t2array[10000000];

int main(void)
{
size_t index;
clock_t start,
end;
size_t array_dim = sizeof array / sizeof array[0];
/* Fill array with random numbers */
for (index = 0; index < array_dim; index++) {
array[index] = rand() / (fabs((double) rand()) + 1);
}
/* Make copies of random numbers */
memcpy(tarray, array, sizeof array);
memcpy(t2array, array, sizeof array);
start = clock();
autoheapsort(array, array_dim);
end = clock();
printf("auto heap sort took %f seconds\n", (end - start) * 1.0 /
CLOCKS_PER_SEC);
start = clock();
heapheapsort(tarray, array_dim);
end = clock();
printf("heap heap sort took %f seconds\n", (end - start) * 1.0 /
CLOCKS_PER_SEC);
start = clock();
etypeheapsort(t2array, array_dim);
end = clock();
printf("etype heap sort took %f seconds\n", (end - start) * 1.0 /
CLOCKS_PER_SEC);
return 0;
}
#endif

dcorbit@DCORBIT64 /c/tmp

Thinking it might be optimization level, I tried again:
dcorbit@DCORBIT64 /c/tmp
$ gcc -W -Wall -ansi -pedantic -DUNIT_TEST -O3 threesorts.c
threesorts.c: In function 'autoswap':
threesorts.c:18: warning: ISO C90 forbids variable-size array 'temp'

dcorbit@DCORBIT64 /c/tmp
$ ./a
auto heap sort took 17.547000 seconds
heap heap sort took 17.267000 seconds
etype heap sort took 15.641000 seconds

dcorbit@DCORBIT64 /c/tmp

I guess that .984 is hardly a dramatic speedup (1.6%) and even the
native type still used 0.89 of the time which is only (11%) better.
 
I

Ian Collins

user923005 said:
When you create a temporary, it will call the constructor. Typically,
the constructor will call new. You are right in the case of
fundamental types like integers and doubles.
No, it will not.

<OT>Constructors don't call new, new calls constructors.</OT>
 
U

user923005

By changing autoswap to this:
void autoswap(void *a, void *b, size_t size)
{
char temp[sizeof(Etype)];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}

So that only C90 is needed (though you have to recompile for every
Etype you want to use) I got this:

dcorbit@DCORBIT64 /c/tmp
$ gcc -W -Wall -ansi -pedantic -DUNIT_TEST -O3 threesorts.c

dcorbit@DCORBIT64 /c/tmp
$ ./a
auto heap sort took 14.719000 seconds
heap heap sort took 15.845000 seconds
etype heap sort took 14.407000 seconds

dcorbit@DCORBIT64 /c/tmp
$

Also, using MS VC++ I got this:
C:\tmp>cl /DUNIT_TEST /W4 /Ox threesorts.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42
for
Copyright (C) Microsoft Corporation. All rights reserved.

threesorts.c
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.

/out:threesorts.exe
threesorts.obj

C:\tmp>threesorts
auto heap sort took 15.532000 seconds
heap heap sort took 16.814000 seconds
etype heap sort took 15.438000 seconds
 
U

user923005

No, it will not.

<OT>Constructors don't call new, new calls constructors.</OT>
<OT>
Are you saying that using the new operator in constructors and the
delete operator in destructors is unusual?
It's not.
</OT>
 
I

Ian Collins

user923005 said:
<OT>
Are you saying that using the new operator in constructors and the
delete operator in destructors is unusual?
It's not.
You're implying the object to be swapped are something other than valid
C types.
If the the objects being swapped require complex initialisation (C or
C++), you probably wouldn't be swapping then, you'd more likely swap
pointers.
 
U

user923005

You're implying the object to be swapped are something other than valid
C types.> </OT>

Right, hence my comment:
"You are right in the case of fundamental types like integers and
doubles."

If the the objects being swapped require complex initialisation (C or
C++), you probably wouldn't be swapping then, you'd more likely swap
pointers.

How will one perform an exchange without a temporary, even when we are
using pointers?
 
D

Daniel Rudy

At about the time of 4/5/2007 4:38 PM, Coos Haak stated the following:
Op Thu, 05 Apr 2007 22:08:12 GMT schreef Daniel Rudy:


Perhaps, but you haven't answered why do you think return is a function?!

I know that return is a keyword for C, But I always code it that way
because that is how I was taught.


--
Daniel Rudy

Email address has been base64 encoded to reduce spam
Decode email address using b64decode or uudecode -m

Why geeks like computers: look chat date touch grep make unzip
strip view finger mount fcsk more fcsk yes spray umount sleep
 
D

Daniel Rudy

At about the time of 4/5/2007 8:48 PM, Ian Collins stated the following:
OK, I've kept out of this so far, but that assumption is wrong. The
temporary is just another automatic variable. Thus the C++ template
solution suffers the same potential stack exhaustion problems as my VLA
suggestion. On the other hand, not having to pass things via void*
gives the compiler a better shot at optimising the copies.

The same applies to sorting in general, this is one area where C++
(through aggressive optimisation) can outperform C where templates
rather than void* are used to provide a generic solution.


Man, you guys are flat ruthless on my coding algorithms and style. My
original one was shot down, then my malloc one caused an argument... In
taking everyone's suggestions into account. I have devised the
following routine:

void swap(void *a, void *b, size_t size)
{
size_t i; /* generic counter */
unsigned char tmp; /* temp */
unsigned char *d1, *d2; /* data pointers */

d1 = a;
d2 = b;
for (i = 0; i < size; i++)
{
tmp = *d1;
*d1 = *d2;
*d2 = tmp;
if (i < (size - 1))
{
d1++;
d2++;
}
}
return;
}


That one doesn't use malloc, swaps 1 byte at a time, and can be used
with any datatype. It will not increment d1 and d2 beyond the size either.
--
Daniel Rudy

Email address has been base64 encoded to reduce spam
Decode email address using b64decode or uudecode -m

Why geeks like computers: look chat date touch grep make unzip
strip view finger mount fcsk more fcsk yes spray umount sleep
 
R

Richard Heathfield

Daniel Rudy said:

Man, you guys are flat ruthless on my coding algorithms and style. My
original one was shot down, then my malloc one caused an argument...

Don't you think that's a good thing?

All of us have been shot down in flames a few times here in c.l.c, and
it has generally been a useful experience.
In
taking everyone's suggestions into account. I have devised the
following routine:

void swap(void *a, void *b, size_t size)

You'll need a header for that size_t.
{
size_t i; /* generic counter */
unsigned char tmp; /* temp */
unsigned char *d1, *d2; /* data pointers */

d1 = a;
d2 = b;
for (i = 0; i < size; i++)
{
tmp = *d1;

Your indenting is broken. said:
That one doesn't use malloc, swaps 1 byte at a time, and can be used
with any datatype. It will not increment d1 and d2 beyond the size
either.

I think you could profitably swap more bytes at a time than that, using
memcpy. Who told you swapping many bytes at once was a bad idea?
 
G

Gunvant Patil

No, you can't take the sizeof void.

[Gunv@nt@ps4064(linux):dumps]$cat test.c
#include <stdio.h>

int main() {
void *p;
printf("Sizeof p : %d\n", sizeof p);
printf("Sizeof *p : %d\n", sizeof *p);
printf("Sizeof void : %d\n", sizeof(void));
return 0;
}

[Gunv@nt@ps4064(linux):dumps]$./out
Sizeof p : 4
Sizeof *p : 1
Sizeof void : 1

-Cheers,
Gunvant
~~~~~~~~
No trees were killed in the sending of this message. However a large
number of electrons were terribly inconvenienced.
 
R

Richard Heathfield

Gunvant Patil said:
No, you can't take the sizeof void.

[Gunv@nt@ps4064(linux):dumps]$cat test.c
#include <stdio.h>

int main() {
void *p;
printf("Sizeof p : %d\n", sizeof p);
printf("Sizeof *p : %d\n", sizeof *p);
printf("Sizeof void : %d\n", sizeof(void));
return 0;
}

foo.c:3: warning: function declaration isn't a prototype
foo.c: In function `main':
foo.c:6: warning: sizeof applied to a void type
foo.c:7: warning: sizeof applied to a void type

Translation: you have been warned. If you choose to proceed regardless,
on your own head be it.
 
K

Keith Thompson

Gunvant Patil said:
No, you can't take the sizeof void.

[Gunv@nt@ps4064(linux):dumps]$cat test.c
#include <stdio.h>

int main() {
void *p;
printf("Sizeof p : %d\n", sizeof p);
printf("Sizeof *p : %d\n", sizeof *p);
printf("Sizeof void : %d\n", sizeof(void));
return 0;
}

[Gunv@nt@ps4064(linux):dumps]$./out
Sizeof p : 4
Sizeof *p : 1
Sizeof void : 1

% gcc -ansi -pedantic test.c
test.c: In function `main':
test.c:6: warning: invalid application of `sizeof' to a void type
test.c:7: warning: invalid application of `sizeof' to a void type

gcc allows certain operations on void, including sizeof, as an
extension. When you invoke gcc in conforming mode, it correctly
issues the required diagnostic for this constraint violation.

The point of the extension is to allow arithmetic on void* as if it
were char*.

In standard C, you can't compute sizeof (void).
 
R

Richard Bos

Gunvant Patil said:
No, you can't take the sizeof void.

[Gunv@nt@ps4064(linux):dumps]$cat test.c
#include <stdio.h>

int main() {
void *p;
printf("Sizeof p : %d\n", sizeof p);
printf("Sizeof *p : %d\n", sizeof *p);
printf("Sizeof void : %d\n", sizeof(void));
return 0;
}

[Gunv@nt@ps4064(linux):dumps]$./out
Sizeof p : 4
Sizeof *p : 1
Sizeof void : 1

Ganuck extensions have no mandatory power outside Ganuck. In _C_, you
can't take sizeof of void.

Richard
 
G

Gunvant Patil

Gunvant Patil said:
[Gunv@nt@ps4064(linux):dumps]$cat test.c
#include <stdio.h>
int main() {
void *p;
printf("Sizeof p : %d\n", sizeof p);
printf("Sizeof *p : %d\n", sizeof *p);
printf("Sizeof void : %d\n", sizeof(void));
return 0;
}

foo.c:3: warning: function declaration isn't a prototype
foo.c: In function `main':
foo.c:6: warning: sizeof applied to a void type
foo.c:7: warning: sizeof applied to a void type

Translation: you have been warned. If you choose to proceed regardless,
on your own head be it.

Yikes..!!! it didn't shown me any warnings when i build that binary
using -Wall.

[Gunv@nt@ps4064(linux):dumps]$gcc -Wall -o out test.c
[Gunv@nt@ps4064(linux):dumps]$gcc -v
Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --
infodir=/usr/share/info --enable-shared --enable-threads=posix --
enable-checking=release --with-system-zlib --enable-__cxa_atexit --
disable-libunwind-exceptions --enable-libgcj-multifile --enable-
languages=c,c++,objc,java,f95,ada --enable-java-awt=gtk --with-java-
home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --host=i386-redhat-linux
Thread model: posix
gcc version 4.0.0 20050519 (Red Hat 4.0.0-8)


-Cheers,
Gunvant
~~~~~~~~
No trees were killed in the sending of this message. However a large
number of electrons were terribly inconvenienced.
 

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,773
Messages
2,569,594
Members
45,113
Latest member
Vinay KumarNevatia
Top