[comp.std.c] void*

gwyn@smoke.BRL.MIL (Doug Gwyn) (04/14/90)

In article <1841@ambush.dk> andrew@ambush.dk (Leif Andrew Rump) writes:
>it is a bit puzzling at first to see that void * is legal but I sure
>rather want that than char * as it was in malloc and other in the past

The trick is to realize that there are no void objects; void denotes
an incomplete type that cannot be completed!  Thus when a pointer of
type void * points to some object, the type of the pointed-to object
cannot be void but must be determined in some other way.  The standard
library routines have void * parameters when they are expected to be
used to operate with a variety of different actual object types.
Since objects are made up of bytes, we COULD have continued to use
char * to point at arbitrary objects, but void * permits stronger type
checking, and that was considered to be a Good Thing.

sah@mips.COM (Steve Hanson) (08/07/90)

	Using void* in function declarations I would think
will be, if not already, a very common abstraction mechanism;
however it seems to have an awkwardness about it.

Here's a straight forward implementation of memchr, assuming
char's are unsigned:

void *memchr(const void *s, int c, unsigned int n)
{
	 register unsigned char C = c;

	 while (--n >= 0)
	         if (*s++ == C) {
                      return --s;
                 }
         return (0);
}

This fails of course because of the dereference and the increment
applied to void*. The dereference problem is easy to remove:


void *memchr(const void *s, int c, unsigned int n)
{
	 register unsigned char C = c;
	 while (--n >= 0)
	         if (*(unsigned char *)s++ == C) {
                      return --s;
                 }
         return (0);
}


however the inc/dec cause a temporary variable to be introduced:

void *memchr(const void *s, int c, unsigned int n)
{
         register unsigned char* S = s;
         register unsigned char C = (unsigned char) c;

         while (--n >= 0)
                 if (*S++ == C ){
	              return (void* )--S;
                 }
         return (0);
}


An alternative would have been to allow void* to be completed
(or inherit its type) by casts. The temporary S wouldn't be
necessary that causes a bit of coding awkwardness and 
inefficiency due to extra store that doesn't lend itself to copy
propagation removal. The code then becomes:

void *memchr(const void *s, int c, unsigned int n)
{
         register unsigned char C = c;

         while (--n >= 0)
                 if (*(unsigned char*)s++ == C ){
	              return (void *) --(unsigned char *)s;
                 }
         return (0);
}

Reasonable defaults would be that the size of what void* points
is the same as the size of char since void* pointers have the
same representation and alignment requirements as a pointer to
a character type.

Since this is all not true today I would expect to see the
idiom:

type func(void* p) {
	type T = p;
	...
	ref T
	...
}

for the most trivial of functions that march through data
structures via void*.

-- 
UUCP: {ames,decwrl,prls,pyramid}!mips!sah
USPS: MIPS Computer Systems, 930 Arques Ave, Sunnyvale CA, 94086

gwyn@smoke.BRL.MIL (Doug Gwyn) (08/07/90)

In article <40624@mips.mips.COM> sah@mips.COM (Steve Hanson) writes:
>however the inc/dec cause a temporary variable to be introduced:

That could be avoided by defining the function as
	void *memchr(const char *s, int c, unsigned int n)
which works because of the "same representation" requirement.

jfh@rpp386.cactus.org (John F. Haugh II) (08/10/90)

In article <13499@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>In article <40624@mips.mips.COM> sah@mips.COM (Steve Hanson) writes:
>>however the inc/dec cause a temporary variable to be introduced:
>
>That could be avoided by defining the function as
>	void *memchr(const char *s, int c, unsigned int n)
>which works because of the "same representation" requirement.

Doesn't this defeat the purpose of (void *) as an abstraction
mechanism?
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh@rpp386.cactus.org

gwyn@smoke.BRL.MIL (Doug Gwyn) (08/11/90)

In article <18498@rpp386.cactus.org> jfh@rpp386.cactus.org (John F. Haugh II) writes:
>In article <13499@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>>In article <40624@mips.mips.COM> sah@mips.COM (Steve Hanson) writes:
>>>however the inc/dec cause a temporary variable to be introduced:
>>That could be avoided by defining the function as
>>	void *memchr(const char *s, int c, unsigned int n)
>>which works because of the "same representation" requirement.
>Doesn't this defeat the purpose of (void *) as an abstraction
>mechanism?

Well, we were talking about a piece of the implementation, not a normal
application (for which one could define the interface differently).
In actuality, I expect most implementations of functions like this
would be written in assembly language, not because C doesn't obtain a
good result, but because customers are perceived as clamoring for the
highest possible speed (in benchmarks, etc.), so it is deemed desirable
from a marketing viewpoint.  If you don't demand that the C version be
utterly optimal, there is no need for the kludge I suggested.

rex@aussie.UUCP (Rex Jaeschke) (08/14/90)

Re the handling of void * in memchr, etc. Doug Gwyn writes:

>That could be avoided by defining the function as
>	void *memchr(const char *s, int c, unsigned int n)
>which works because of the "same representation" requirement.

I like Doug's suggestion but it did raise another question. 

It's obviously useful to put a prototype in a header and include it
at the site of every call.  However, it's also a good idea to include
it where the function is actually defined.  If one uses Doug's
approach, will the compiler cough or not? For example:


void *memchr(const void *s, int c, unsigned int n);

void *memchr(const char *s, int c, unsigned int n)
{
	/*...*/
}

I tried this on 6 DOS-based compilers all of which CLAIM to be 
either ANSI-conformant or VERY close and they came up 3/3 on whether 
the two function declarations were "compatible" (I'm not sure that's the 
correct term to use here though).

Certainly void * and char * are required to have identical 
representation. However, the two types are different types. Should a 
REAL ANSI C compiler complain or not?

Rex

----------------------------------------------------------------------------
Rex Jaeschke     |  Journal of C Language Translation  | C Users Journal
(703) 860-0091   |        2051 Swans Neck Way          | DEC PROFESSIONAL
uunet!aussie!rex |     Reston, Virginia 22091, USA     | Programmers Journal
----------------------------------------------------------------------------
Convener of the Numerical C Extensions Group (NCEG)
----------------------------------------------------------------------------

gwyn@smoke.BRL.MIL (Doug Gwyn) (08/14/90)

In article <43.UUL1.3#5077@aussie.UUCP> rex@aussie.UUCP (Rex Jaeschke) writes:
>Certainly void * and char * are required to have identical 
>representation. However, the two types are different types. Should a 
>REAL ANSI C compiler complain or not?

Yes, I think a diagnostic is required.  That's why the implementation of
memchr() along the lines I suggested should not include the standard
header.  Indeed, I wouldn't include the relevant header in most
implementations of standard library functions, since it usually isn't
either necessary or useful to do so.

karl@haddock.ima.isc.com (Karl Heuer) (08/16/90)

In article <13548@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>That's why the implementation of memchr() along the lines I suggested should
>not include the standard header.  Indeed, I wouldn't include the relevant
>header in most implementations of standard library functions, since it
>usually isn't either necessary or useful to do so.

I would consider it bad style to take advantage of the same-representation
clause like that; I believe it was added to grandfather in the pre-ANSI code
that contained explicit declarations for functions like malloc().  I would
simply implement memchr() using `void *', with casts as appropriate.  It's a
minor wart.

For memchr(), you want the header anyway in order to get the definition of
size_t.  (You could get it from some other header, but why go out of your way
to be difficult?)  Even if this were not the case, I'd include it just to let
the compiler confirm that the prototype in the header agrees with that in the
source.

Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint

seanf@sco.COM (Sean Fagan) (08/16/90)

In article <17416@haddock.ima.isc.com> karl@kelp.ima.isc.com (Karl Heuer) writes:
>In article <13548@smoke.BRL.MIL> gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>>Indeed, I wouldn't include the relevant
>>header in most implementations of standard library functions, since it
>>usually isn't either necessary or useful to do so.

And, of course, don't forget that the implementor can use whatever
non-standard extensions in the library source that she or he wants to.  Such
as using gcc's extension that has sizeof(*(void *)) == 1.  That would
eliminate the need for casts for things like malloc, memcpy, etc.

Karl *did* make a good point, that I deleted, about using the "standard"
include files to make sure the prototypes agree.  But remember that the
libraries might not be writtin in C at all, and the prototypes either
directly copies from the standard (with, hopefully, the parameter names
removed 8-)), or generated by some utility.

Just my $0.02 (Canadian).

-- 
Sean Eric Fagan  | "let's face it, finding yourself dead is one 
seanf@sco.COM    |   of life's more difficult moments."
uunet!sco!seanf  |   -- Mark Leeper, reviewing _Ghost_
(408) 458-1422   | Any opinions expressed are my own, not my employers'.

gwyn@smoke.BRL.MIL (Doug Gwyn) (08/16/90)

In article <17416@haddock.ima.isc.com> karl@kelp.ima.isc.com (Karl Heuer) writes:
>I would simply implement memchr() using `void *', with casts as appropriate.

If you recall the original complaint, you should see that casts do not solve
the problem under discussion.