Common misconceptions about C (C95)

W

Willem

gwowen wrote:
)
)> In short, it's bad form to call functions without a prototype.
)
) So if I use a C99-mode compiler (or -Wmissing-prototypes or
) equivalent), the major (sole?) reason for not casting vanishes, right?

No, another good reason for not casting is that you can change the type
of whatever you're malloc()ing without also having to change the casts.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
S

spinoza1111

gwowen wrote:

)
)> In short, it's bad form to call functions without a prototype.
)
) So if I use a C99-mode compiler (or -Wmissing-prototypes or
) equivalent), the major (sole?) reason for not casting vanishes, right?

No, another good reason for not casting is that you can change the type
of whatever you're malloc()ing without also having to change the casts.

Part of strong typing should be the assignment of type at detailed
design time, not coding time. The programmer should not decide, for
some silly reason of "efficiency", that all values will be a too-short
type (a common mistake). Therefore, it should NOT be all that easy to
modify type.

Alternatively, the preprocessor could define the type and then it can
be changed in a single shot.
 
B

Bruce Cook

spinoza1111 wrote:
[...]
And today, an int has 32 bits because today, 64 bits are in common use
yet common parlance recognizes the fact that 64 bits provides far more
precision than will ever be needed by most applications, and it is
unlikely that we shall need more. Therefore it makes sense in common
CS parlance to refer to the 64 bit integer as long, which entails
referring to the 32 bit integer as int. It is to be a troll from the
dark ages to want to call a 16 bit integer int, a 32 bit integer long,
and a 64 bit integer long long, or at a minimum it is to want to live
in the past.

True for the large number of programmers working on modern commodity general
purpose compute platforms, often not true for those of us working with micro
controllers.

Bruce
 
P

Phil Carmody

Keith Thompson said:
Really? Why? I'm geniunely curious.

If you really have a legitimate need to compile the same code as C
and as C++, I think you'd be the third such person I've seen here.
(One of the others was P.J. Plauger.)

I have a library of mathematical helper functions which I want
inlined in both C and C++ code.

Phil
 
I

Ian Collins

spinoza1111 said:
"Superfluity" is not an argument since in a language like C it's
important to document intent.

What intent?

pc = malloc( sizeof(*pc) );

Tells any programmer the author intents to allocate enough memory for
whatever pc is a pointer to. What else is there to say?
Smart programmers don't quote sigs or google crap.
 
S

spinoza1111

spinoza1111wrote:


What intent?

pc = malloc( sizeof(*pc) );

Tells any programmer the author intents to allocate enough memory for
whatever pc is a pointer to.  What else is there to say?

What type of thing pc points to. This needs to be an "internal" as
opposed to an "external" property of pc. If you don't like it, use
assembler like a man. C deceives.

Your mental block is the failure to realize that different TYPES of
things SHOULD NOT be spoken of in the same way as commensurate or
fungible. This is in turn caused by commodity mystification.


Smart programmers don't quote sigs or google crap.
 
S

spinoza1111

spinoza1111wrote:
[Childish things]

When you become 18 years old, drop us a note.

I'd rather say what you consider "childish" things than come in here
like Rosenau repeating his twisted understandings of clc shibboleths
and trying to restart World Wars I & II with Navia in order to curry
favor with the clc Tiki (and Kiki) gods.
 
S

spinoza1111

spinoza1111wrote:
[Childish things]
When you become 18 years old, drop us a note.

I recall that your own introduction to comp.lang.c (some years ago)
was not entirely smooth. It took you a while to settle in; do you
remember that?

Give him another 280, 290 years or so. to learn the lessons that you
learned so quickly.

Kept in a staight of infancy by corporate employment, you've created a
fantasy adult world for yourself here where you are "senior" because
you know "more", but what you "know" is toxic waste. You are an
incompetent programmer as witness your failure at the C++ test, and
your attacking the test (a typical gesture of the incompetent).
 
D

dragan

Ioannis said:
I have created a text available under GNU FDL 3 (Free Documenation
License) or later, regarding "Common misconceptions about C" (C95).

I shun anything GNU.
 
I

Ian Collins

spinoza1111 said:
What type of thing pc points to.

Why should it matter? Anyway, pc should be declared close by if for
some reason the reader cares.
Smart programmers don't quote sigs.
 
S

spinoza1111

spinoza1111wrote:


Why should it matter?  Anyway, pc should be declared close by if for
some reason the reader cares.

We're saying that the type of an identifier is no accident, or at
least less of an accident than incompetent programmers believe. Even
if pc is "close by" it can still be off the screen for the same reason
a British officer was heard to say in 1914 that "a battle is that
which takes place at the boundary between two maps".

But hey, if it floats your boat to not use a cast with malloc, knock
yourself out because the most common misconception about C is that it
is still even viable as a programming language.

Smart programmers don't quote sigs.
 
K

Kaz Kylheku

I don't think so. I think the implicit conversion from (void *) is a
feature, not a bug. It allows us to express the notion of an address,
the contents of which are not known.

If the contents are not known, why is void * ready to convert into
a pointer to any object type with merely an assignment?

The void * idea came from C++.

Its design should have been appreciated, respected and taken as-is.
I don't see any point to requiring or performing the cast.

You could say that about /any/ conversion which is not actually
changed to a different conversions by the presence of a cast.
If you assign an (int *) to a (double *), the only conversion that
can possibly take place there is from the former to the latter type.
Why don't we drop the diagnosis for that? Because then we we are
back to the B language.

So what is the point? The point is that a C++ program which subverts the type
system via a conversion, but does not require a diagnostics, contains a cast.
A C program which subverts the type system via a conversions, without requiring
any diagnostics, does not contain a cast.

/* C: this could be a (void *) to (some_type *) conversion.
foo->x = *bar;

/* C++: this cannot be a (void *) to (some_type *) conversion */
foo->x = *bar;

You can't perform a targetted code inspection for misuses of type, on a large
body of code, if the only feature which denotes bad conversions is /unadorned/
assignment, initialization, argument passing and function returning---the
same syntax which also denotes safe conversions and non-conversions.

Programming isn't a contest about who produces the code with the fewest number
of keystrokes. You already /can/ do what you want; the cast syntax is a small
price to pay. Be glad you arent't working in some straitjacketed language in
which you simply can't do that kind conversion, but don't forget to say
``thanks'', which is spelled (<something> *).
 
K

Kaz Kylheku

This rationale only makes sense to a dyed-in-the-wool OOP programmer who has
no qualms inventing an interface hierarchy just so he can "properly" cast
between objects for passing to simple generic routines.

Use (or abuse) of void pointers has little to do with people bit-twiddling

I didn't say don't use void pointers, but use another type in their place,
which doesn't have the hole of cast-free, undiagnosed conversion.
object representations and more to do with an honest tradeoff between static
typing and programmer time.

``I want to save a few keystrokes and not want to type (foo *) to flag
an unsafe conversion, which I'm allowed to perform'' is not part of any honest
tradeoff.
 
K

Keith Thompson

Malcolm McLean said:
Actually void *s need to have the same representation as char *s, because an
unsigned char * can represent any data type and be converted without loss.

Yes, but they're still distinct types.
It's one of those little quirks that teels us that void * was added to the
language as an afterthought.

A lot of C is like that.
 
K

Keith Thompson

Kaz Kylheku said:
You could say that about /any/ conversion which is not actually
changed to a different conversions by the presence of a cast.
If you assign an (int *) to a (double *), the only conversion that
can possibly take place there is from the former to the latter type.
Why don't we drop the diagnosis for that? Because then we we are
back to the B language.

I don't understand your point about int* and double*. Are you
saying that a conversion from double* to int* makes more sense than
a conversion from int* to double*? Either conversion potentially
has undefined behavior.

[...]
 
I

Ioannis Vranos

Malcolm said:
Actually void *s need to have the same representation as char *s, because
an unsigned char * can represent any data type and be converted without
loss.

It's one of those little quirks that teels us that void * was added to the
language as an afterthought.


void pointers are useful for generic programming in C, for example writing a
function that works for many types.


In C++, generic programming is done by using templates and template
specialisations; and with non-template function overloading.


So a form enabling generic programming was and is necessary in C.


Of course, generic programming done with void pointers is less efficient
than C++ templates and function overloading - because it usually requires
and identification argument and selections (ifs/switch) , and I think a
future C standard should introduce the ability for template functions and
function overloading.


An example of generic programming in C:


enum id { Character, Short, Int, Long };


void* add(void *pArg1, void *pArg2, id identity);



--
Ioannis Vranos

C95 / C++03 Software Developer

http://www.cpp-software.net
 
K

Keith Thompson

Kaz Kylheku said:
Here, these two types are just an example. Substitute any other
types.

In any conversion between pointer types (on assignment, initialization,
argument passing, or returning from a function) it's always clear which
conversion the program wants, even if there is no cast.

Ok, you meant specifically in the context of assigning an int* to a
double*. (Which is what you actually wrote; my fault for
misunderstanding it.)
So the cast doesn't actually /do/ anything in the sense of contributing
to the semantics of computation.

Sometimes cast /do/ something, like (double) x / y, where x and y
are of type int.

Right, numeric conversions like this are one of the (relatively few)
contexts where I agree that casts are appropriate.
That doesn't amount to an argument for removing all unnecessary conversion
diagnostics from the language, so that these useless do-nothing casts don't
have to be used.

The casts do in fact do something: they suppress a diagnostic, but leave
a written record of where it occured (or would have occured).

The void * type also gives you a way to suppress a diagnostic. But in
a stupid way which doesn't flag the place where the conversion is happening.
Any of these pieces of syntax could be an unsafe conversion in C
from the type void *:


func(ptr); /* argument of void * type */

return ptr; /* return type of void * type */

foo->memb = ptr[3]; /* memb is array ofo void * */

int *x = func(); /* func returns void */

Just by looking at any of these places, you don't see that the
type system is being subverted.

This is a bad way to save programming keystrokes.


So don't do that.

But I argue that, in the specific case of the result of malloc(),
void*, with all the semantics C defines for it, really is the
appropriate type. malloc() returns a pointer to some chunk of memory,
a pointer which, because of the defined semantics of malloc(), can be
safely assigned to a target of any pointer-to-object type.

Other languages provide type-specific allocators. For example, C++'s
"new" operator yields a pointer of a type determined by the type
argument; ``new int'' yields an int*. Another language with a more
sophisticated type system might use some kind of type hierarchy. C
just provides a single special case generic pointer type. Sure, it
can be abused:
some_type *p = ...;
void *tmp = p;
another_type *bad = tmp;
But it can also be used as it was intended:
some_type *p = malloc(sizeof *p);

A pointer cast typically indicates that you're doing something
dangerous, such as interpreting a pointer, or even an integer, as
if it were a pointer of a different type. Seeing a pointer cast
should raise a red flag: this is potentially non-portable code.
I don't want to plant such a red flag on an assignment of the result
of malloc() to a pointer object, something that's perfectly type-safe
and well defined.

Again, a more sophisticated language than C might provide a
way to distinguish between safe and unsafe pointer conversions.
Since C doesn't, my preference is to use casts only for the unsafe
conversions.

Permitting conversions to and from void* without casts wasn't an
accident; it was a deliberate language design choice. If you insist
on casting the result of malloc(), you're fighting against the
language.
 
K

Keith Thompson

Keith Thompson said:
Again, a more sophisticated language than C might provide a
way to distinguish between safe and unsafe pointer conversions.
Since C doesn't, my preference is to use casts only for the unsafe
conversions.

Permitting conversions to and from void* without casts wasn't an
accident; it was a deliberate language design choice. If you insist
on casting the result of malloc(), you're fighting against the
language.

Something I forgot to ask: Do you cast the arguments to memcpy() and
similar functions? For example:

double arr1[SOME_SIZE];
double arr2[SOME_SIZE];
...
memcpy((void*)arr2, (void*)arr1, sizeof arr2);
vs.
memcpy(arr2, arr1, sizeof arr2);
 
K

Kaz Kylheku

The traditional bug that not casting malloc() catches is when malloc()
is not declared (because the relevant #include is omitted) and thus
defaults to returning int. C's strong typing would normally catch
that error, but is defeated by the cast. That was why I used "int" in
my example.

When you haven't made that mistake, the cast makes no difference
either way in terms of type safety.

It only makes no difference either way because it can be removed,
and no diagnostic occurs.

And that's because the void * type is there.

If you make your own custom allocator, or malloc wrapper, which doesn't return
void *, then the situation changes:

unsigned char *my_malloc(size_t size);

Now you have to have the cast. And it makes a difference in type safety
because if you don't have it, there is a diagnostic. Your program
is either flagged with the diagnostic, or it's flagged with the cast.
 

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
474,008
Messages
2,570,268
Members
46,867
Latest member
Lonny Petersen

Latest Threads

Top