I have some functions which take as i/p a buffer (it can be float, char,
or 16 bit, int etc.). The result is another o/p buffer, its type is also
flexible (it could be a float, char etc.).
I try to pass both as "void *buf" so that it can accept any data type.
But since I access the buffer and try to assign its elements to another
I get compile errors (I have pasted at the end).
Now my question is how can I pass the i/p and o/p buffers as args to the
function without hardcoding the data type, someone earlier suggested to
pass another argument which tells the type, e.g.
myfunction(void * buf, 1, ...) 1 may indicate that buf is char type, 2
means 16 bit etc.
But that means having if .. else's inside the function to do the same
thing. Is there any other way?
Here is a bit pattern, 32 bits long:
11000101001110101001110101000110
What does it mean? What value(s) does it represent?
Does your answer change if I tell you it is the raw bits of a 32-bit
"float"? What if I say it is a 32-bit VAX float, or perhaps a 32-bit
SPARC float? Suppose I say it is, instead, a 32-bit "int", or a
32-bit "signed int". Now what value(s) can it represent? What if
it is an array of four "char"s or four "unsigned char"s?
(Finding answers -- there may be more than one -- to the above
questions is a good and useful exercise, that will help you understand
how the system(s) you use interpret bits. Note that different
systems interpret the same bits differently! One reason we use
"high level" source code -- although C is relatively low level for
a high level language -- is to avoid having to specify the precise
set of bits, which change from system to system, when we can get
away with specifying only what we want the bits to mean. The
source-code number "3.1415" or "42" has a definite meaning, and
the fact that machines A and B use different bit-patterns to
represent them is -- we hope -- irrelevant.)
Note that all of these are 32 bits long. They all look exactly the
same when treated as a 32-bit-long bit-string. But they all represent
different values.
When you use "void *" in C, you are -- usually -- implicitly using
a "bag of uninterpreted bits" type. Any interpretation necessarily
arises from giving the "bag of bits" some more-specific type. You
must decide whether you intend to interpret the bits, and if so, how.
This will dictate the type(s) you must provide.
Note that a series of if/else's, or a switch, inside a function
that receives a "void *" but wants to interpret the bits as one
of (say) the two possibilities "int" and "float" will result in C
code looking like:
enum whichtype { TY_INT, TY_FLOAT };
/*
* Here p points to the first bit(s) in the bag-o-bits, "len"
* is the number of items of some interpreted type, and "ty"
* is the enumeration saying which type.
*/
void f(void *p, size_t len, enum whichtype ty) {
size_t i;
int *ip;
float *fp;
switch (ty) {
case TY_INT:
ip = p;
... work with ip
, where i is between 0 and len-1, e.g:
for (i = 0; i < len; i++)
ip *= 2;
break;
case TY_FLOAT:
fp = p;
... work with fp, where i is between 0 and len-1 ...
for (i = 0; i < len; i++)
fp *= 2.0;
break;
default:
panic("invalid type argument %d to f()", (int)ty);
/* NOTREACHED */
}
}
Not only do you really, truly have to write the if/else or switch,
you will also find that, on typical machines, it compiles to code
that uses different machine instructions to manipulate the data.
The code using ip might look like, e.g.:
# this doubles ip, where i is in %eax and ip is in %edx
movl (%edx,%eax,4),%ecx
addl %ecx,%ecx
movl %ecx,(%edx,%eax,4)
versus:
# this doubles fp, where i is in %eax and fp is in %edx
flds (%edx,%eax,4)
fadds (%edx,%eax,4)
fstps (%edx,%eax,4)
These completely-different machine instructions are utterly necessary
here -- using the "addl %ecx,%ecx" instructions will not result in
doubling a "float" value in fp, but rather will just make a mess
of the value.
Thus, different C code is required because different machine code
is required. Using "void *" absolves neither you nor the compiler
of this.
If you do enough of this sort of thing (or analyse your systems
carefully), you will find that there are cases where the C source
code must be different, yet the underlying machine code on your
machine(s) is the same. This happens because (and when) the abstract
meaning defined in C for two different types happens to be implemented
using a single underlying machine-level mechanism. But in general,
the C code should remain separate, because on some *other* machine(s),
this may no longer be the case. In other words, in the C code,
you write *what* you want to have happen, not *how* to *make* it
happen. If you want different things, at the C level, that all
happen to be done the same way at the machine level, it is the
compiler's job to discover this and optimize the code appropriately.
(Some compilers are better than others at this, and you may sometimes
find yourself having to "help" some compilers a bit. Do this only
when you really know what you are doing. )