Common misconceptions about C (C95)

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.

Right and then this so-called ``strong typing'' lets you call an undeclared
function.

This argument is complete nonsense because if your compiler is letting
you call undeclared fucntions, you've got a whole class of problem.

double y = fabs(x); /* not declared, assumed to return int */

This needs to be solved by using a compiler which warns about
the use of functions that have not been declared by a prototype.
 
K

Kaz Kylheku

spinoza1111 said:
[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?

He never settled in. The newsgroup was merely reduced to his level since then.
 
K

Kaz Kylheku

enum {
H_CONTENT_LENGTH,
H_CONTENT_TYPE,
};

struct http_content_length { int type; /*... */ };
struct http_content_type { int type; /* ... */ };

void http_content_length_print(struct http_content_length *, FILE *);
void http_content_type_print(struct http_content_length *, FILE *);


void http_header_print(void *header, FILE *fp) {
static void (*const print[])() = {
[H_CONTENT_LENGTH] = &http_content_length,
[H_CONTENT_TYPE] = &http_content_type,
};

Okay, you'er using C99 designated initializers. And your function pointer
type is old style with (). This kind of code makes me go huh?
int type = *(int *)header;
/* ... check bounds */
print[type](header, FILE *fp);
}

This can be done with the struct hack

struct anything { int type; };

struct http_content_type { int type; .... }

union generic {
struct anything any;
struct http_content_type hct;
...
};

Now you can pass around ``union generic *obj'' pointers, of which you
can always access ``obj->any.type'' without casting.

The type could be a pointer to some info instead of an integer
code. Or both.

struct anything { struct operations *ops; ... };

struct http_content_type { int type; .... }

struct http_content_type { int type; .... }
struct operations {
void print(union generic *, FILE *);
...
}

Now you can just print an object using

ptr->any.ops->print(ptr, stdout);

For every object type, you have to define an operations structure
which is filled in, similarly to your print[] array.
 
E

Eric Sosman

Kaz said:
enum {
H_CONTENT_LENGTH,
H_CONTENT_TYPE,
};

struct http_content_length { int type; /*... */ };
struct http_content_type { int type; /* ... */ };

void http_content_length_print(struct http_content_length *, FILE *);
void http_content_type_print(struct http_content_length *, FILE *);


void http_header_print(void *header, FILE *fp) {
static void (*const print[])() = {
[H_CONTENT_LENGTH] = &http_content_length,
[H_CONTENT_TYPE] = &http_content_type,
};

Okay, you'er using C99 designated initializers. And your function pointer
type is old style with (). This kind of code makes me go huh?

Perhaps you have misread C99. The attempt to call an
undeclared function now produces a diagnostic, whereas in C90
it was allowed (and had well-defined behavior, albeit sometimes
unwelcome). But neither C90 nor C99 requires that a function
declaration or definition provides a prototype. Nor does either
Standard require that a function pointer be declared with a
prototype.

Or perhaps not. One can, of course, "go huh?" over anything
at all, like the amount and arrangement of white space. So, too,
can the compiler, by emitting diagnostics for well-defined code.
"Taste is skin-deep in the eye of the beholder," or something
along those lines.
 
K

Kaz Kylheku

Kaz said:
enum {
H_CONTENT_LENGTH,
H_CONTENT_TYPE,
};

struct http_content_length { int type; /*... */ };
struct http_content_type { int type; /* ... */ };

void http_content_length_print(struct http_content_length *, FILE *);
void http_content_type_print(struct http_content_length *, FILE *);


void http_header_print(void *header, FILE *fp) {
static void (*const print[])() = {
[H_CONTENT_LENGTH] = &http_content_length,
[H_CONTENT_TYPE] = &http_content_type,
};

Okay, you'er using C99 designated initializers. And your function pointer
type is old style with (). This kind of code makes me go huh?

Perhaps you have misread C99.
The attempt to call an
undeclared function now produces a diagnostic, whereas in C90
it was allowed (and had well-defined behavior, albeit sometimes

Where is there a call to any undeclared function above?
unwelcome). But neither C90 nor C99 requires that a function
declaration or definition provides a prototype. Nor does either
Standard require that a function pointer be declared with a
prototype.

Old-style declarations for functions and pointers are obsolete crud;
it's very peculiar to see the usage mixed with C99 features.

On closer inspection, the code above is in fact abusing the weakness
in the old-style declarations to do undefined type punning with function
pointers. The functions give rise to this pointer type:

void (*)(struct http_content_length, FILE *);

but after being stuffed into the under-typed array, they are being called with
this expression:

print[type](header, FILE *fp);

This treats them as having the type

void (*)(void *, FILE *);

But let's talk about my weak reading comprehension with respect to C99.
 
S

Seebs

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

Because in C, we trust the programmer.
The void * idea came from C++.
Its design should have been appreciated, respected and taken as-is.

Well, which is it? Did it come from C++, or should we respect it and
take it as is? :p
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.

That's because, in C, it's pretty useful to be able to talk about pointers
independant from the question of what they point to, because C tends towards
a lower-level view of the world.
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> *).

I prefer the cast syntax to be reserved for cases where the conversion isn't
obvious or automatic. I don't want to have to cast to or from (void *) for
the same reason that you don't necessarily have to cast all integer
conversions.

-s
 
B

Ben Bacarisse

William Ahern said:
I personally make use of the following construct often, though it assumes
compatibility among object and void pointers. My question is, how would
requiring casting--from a common type to the specific type--inside
http_content_length_print() or http_content_type_print() improve anything? I
admit I'm playing a little fast-and-loose with C's typing, but what
_exactly_ am I losing in terms of type safety, notwithstanding the pointer
compatibility assumption? In other words, how would those two functions be
any better if the very first lines were like `struct http_content_lenth *hdr
= (struct http_content_length *)arg0;'? They couldn't be.

You've picked a bad example because there a systems on which the code
outlined below won't work. One fix is to do what you suggest here:
make the function take a void * and convert in the function itself.
The other fix is to call the function through a pointer whose type
forces the correct conversion.
Which means that
whether you make use of a void pointer in http_header_print(), at some point
you're ability to ensure type-safety is lost. Thus, at some level of
complexity using void pointers incurs no loss. Again, that is a true
statement no matter whether requiring casting to pass arguments to
http_header_print() reduces bug (and I won't dispute that point, though it
_is_ arguable on other grounds, such as the fact that people can get into
the habit of casting as a matter of course so the fact that a programmer
typed a certain series of keys guarantees little if anything).


enum {
H_CONTENT_LENGTH,
H_CONTENT_TYPE,
};

struct http_content_length { int type; /*... */ };
struct http_content_type { int type; /* ... */ };

void http_content_length_print(struct http_content_length *, FILE *);
void http_content_type_print(struct http_content_length *, FILE *);


void http_header_print(void *header, FILE *fp) {
static void (*const print[])() = {
[H_CONTENT_LENGTH] = &http_content_length,
[H_CONTENT_TYPE] = &http_content_type,

Presumably you meant &http_content_length_print and
&http_content_type_print here.
};
int type = *(int *)header;
/* ... check bounds */
print[type](header, FILE *fp);

On, say, a word addressed machine there is no point at which the
conversion from one type of pointer to another can happen. If print
is declared:

static void (*const print[])(struct http_content_length *, FILE *)
= {...}

then it will work. Alternatively you need to declare

void http_content_length_print(void *, FILE *);
void http_content_type_print(void *, FILE *);

and convert inside the function as you describe in the text.

I tend to agree with your point, but the automatic conversion from/to
void * only works when enough type information is available for it to
happen.
 
B

Ben Bacarisse

Kaz Kylheku said:
On closer inspection, the code above is in fact abusing the weakness
in the old-style declarations to do undefined type punning with function
pointers. The functions give rise to this pointer type:

void (*)(struct http_content_length, FILE *);

You missed a * in the first parameter.
but after being stuffed into the under-typed array, they are being
called with this expression:

print[type](header, FILE *fp);

This treats them as having the type

void (*)(void *, FILE *);

Which really would break (rather than just being undefined under the
letter of the standard) on my personal DS9000 -- a word addressed
machine that had three kinds of pointer: byte pointers (for char * and
void *), word pointers (for pointers to anything bigger than char) and
function pointers that were of a different size and representation
altogether. C worked fine, but you really had to stick to the rules.

To address you larger point, I think you have a point but I don't
agree that there is enough to be gained but altering the behaviour of
void *. I find that the presence of void * in a data structure or a
function prototype is enough to know where to check for inappropriate
type shenanigans. This leaves pointer casts as an even bigger red
flag: they usually mark places where type punning is happening an
where greater care with alignment and such like is needed.

<snip>
 
G

gwowen

Because in C, we trust the programmer.

I rather suspect that's not the reason. (Although it is a nice glib
one-liner.) Rather, I suspect that the void* -> anything* conversion
exists simply because malloc() has to return something, and this
compromise does least damage to C's type system (such as it is), while
preventing the already-discussed no-prototype errors.

"We trust the programmer" is particularly ironic, given that the
strongest argument for not casting malloc is that it enables the
compile detection of unprotyped malloc(). Perhaps you meant "In C we
trust the programmer to keep track of the types pointed to by void*,
but we don't trust him to remember to include stdlib.h".

If anyone *really* trusted the programmer, there'd be no need for any
data pointer types except void*.
 
B

Ben Bacarisse

gwowen said:
I rather suspect that's not the reason. (Although it is a nice glib
one-liner.) Rather, I suspect that the void* -> anything* conversion
exists simply because malloc() has to return something, and this
compromise does least damage to C's type system (such as it is), while
preventing the already-discussed no-prototype errors.

Hmm... What no-prototype errors? void * does a reasonable job but it
can't help where there is no prototype. The example shown doesn't
work precisely because of a missing prototype.

<snip>
 
E

Eric Sosman

gwowen said:
I rather suspect that's not the reason. (Although it is a nice glib
one-liner.)

It's straight from the Rationale that the committee wrote
to explain the reasons for some of their decisions. You'll
find it as the first -- the *first* -- bullet point in the
section headed "Keep the spirit of C" (Rationale, Section 0).
If anyone *really* trusted the programmer, there'd be no need for any
data pointer types except void*.

Reaction #1: Nonsense. With only one kind of pointer, how
would the programmer describe "how much" data to load or store
on a dereference?

Reaction #2: That language was B.
 
K

Kenny McCormack

Richard said:
For someone that professes no desire to be in a clique he sure uses the
"we" word an awful lot.
Yup.

His desire to be part of Heathfield's inner harem permeates the c.l.c
air like an overripe scallop ...

Oh no. Not at all. As we all know, Seebs is completely unmotivated by
"status". He said so himself.
 
G

gwowen

It's straight from the Rationale that the committee wrote
to explain the reasons for some of their decisions.  You'll
find it as the first -- the *first* -- bullet point in the
section headed "Keep the spirit of C" (Rationale, Section 0).

What nonsense.

The free conversion of void* predates that rationale by at least 10
years so that is, at best, a post-hoc justification. Indeed, how
could "Keep the spirit of C" be a rationale in the design of C? That
rationale explains why standardization didn't change the behaviour of
void*, but it doesn't explain how that behaviour came to be.

Incidentally, are there any misfeatures of any language that could not
be rationalized by saying "we trust the programmer"?
Reaction #1: Nonsense. With only one kind of pointer, how would the programmer describe "how much" data to load or storeon a dereference?

Why, trust the programmer to specify it, of course.
 
R

Richard Bos

gwowen said:
So if I use a C99-mode compiler (or -Wmissing-prototypes or
equivalent), the major (sole?) reason for not casting vanishes, right?

Wrong.

The major reason for not using superfluous casts, of which malloc()
casts are only the most common category, is that it damages the
programmer's ability to treat a cast as a warning sign.

Richard
 
G

gwowen

Wrong.
The major reason for not using superfluous casts, of which malloc()
casts are only the most common category, is that it damages the
programmer's ability to treat a cast as a warning sign.

So says the received opinion. Personally, I prefer the type safety
that comes from me asserting the type of object allocated on the RHS,
and letting the compiler enforce.

To me,

malloc(unsigned_num_elements * sizeof(type_t))

has an implicit type of type_t*. I cast to make that explicit, and
then the compiler can catch type errors. History has shown that I am
more likely to make those kind of mistakes than forget to prototype
malloc, and my compiler checks for missing prototypes anyway. I don't
need the (minor, nebulous) benefits of not casting pointing out. I
understand them well enough, thank you. I prefer the (minor,
nebulous) benefits of casting, thanks. Experience has shown that that
is what works for me. On matters of taste, it is possible to have
thought it through, and reached a different conclusion from the
majority, you know.

If you don't think like that, fine. If you don't make those errors,
lucky you, well done. But its idiotic to pretend your personal
preference is some sort of hard and fast rule that enables you to tell
good programmers from bad. It's a guideline, not an article of faith.

However, since so many people clearly think that way, may this will
makes you feel better and stop the mouths from foaming:

You're way is the one true way. I'm completely wrong, I repent.
yadda yadda yadda.
 
I

Ioannis Vranos

gwowen said:
What nonsense.

The free conversion of void* predates that rationale by at least 10
years so that is, at best, a post-hoc justification. Indeed, how
could "Keep the spirit of C" be a rationale in the design of C? That
rationale explains why standardization didn't change the behaviour of
void*, but it doesn't explain how that behaviour came to be.

Incidentally, are there any misfeatures of any language that could not
be rationalized by saying "we trust the programmer"?


Why, trust the programmer to specify it, of course.


As far as I now, the casting of the returned value of malloc(), is from the
pre-standard time when it returned a char *, and thus it was necessary.

Now that malloc() returns a void pointer, it is not needed.



--
Ioannis Vranos

C95 / C++03 Software Developer

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

Ioannis Vranos

Tristan said:
I see you're just as incorrigible now as when you first appeared on this
newsgroup ten years ago. :)
:)


LOL. :)


Well, web pages are made based on some specifications, screen resolution,
etc.

My specifications are probably a bit high, however I am writing and viewing
my pages with my laptop, and I am using its specifications.



I mention the specifications in the page.



--
Ioannis Vranos

C95 / C++03 Software Developer

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

lawrence.jones

gwowen said:
What nonsense.

The free conversion of void* predates that rationale by at least 10
years so that is, at best, a post-hoc justification.

The Rationale was written contemporaneously with the original ANSI C
Standard. It has been continually updated since then, but the rationale
for void* was in the original.
Indeed, how could "Keep the spirit of C" be a rationale in the design
of C?

It wasn't; it was a guiding principle for the standardization of C.
That rationale explains why standardization didn't change the
behaviour of void*, but it doesn't explain how that behaviour came to
be.

void* didn't exist in C before standardization; it was designed by the
committee, guided by the above principle.
 
E

Eric Sosman

gwowen said:
What nonsense.

What are you describing as "nonsense?" Are you saying that
the Rationale does not contain the text I describe? Or that my
description of it is incorrect? Or what?

The Rationale is available to anyone who wants to read it
and decide which of us is right about its content.
The free conversion of void* predates that rationale by at least 10
years so that is, at best, a post-hoc justification.

"What nonsense." The original Rationale was issued along with
the original ANSI Standard in 1989. In 1979 C didn't have silent
conversion to or from void* for the simple reason that it didn't
have a void* type at all. (Well, before the original Standard there
were lots of loosely related languages floating around describing
themselves as "C," and it's possible that one or more of these
dialects had a void*. But in 1979? Can you point to even one
example from that date?)
Indeed, how
could "Keep the spirit of C" be a rationale in the design of C?

Because the committee didn't create C out of thin air, but by
trying to reconcile and improve upon the assorted dialects mentioned
above. That pre-Standard C had (in the committee's view) a "spirit"
that they wished to preserve.
That
rationale explains why standardization didn't change the behaviour of
void*, but it doesn't explain how that behaviour came to be.

Yes, it does. You'll find the explanation of void* (and other
"created" types) in Section 6.5.2 of the Rationale.
[... with trust, "all you need is void*" ...]
Reaction #1: Nonsense. With only one kind of pointer, how would the programmer describe "how much" data to load or storeon a dereference?

Why, trust the programmer to specify it, of course.

Using what syntax? What is it about pointers p and q that
says one of them refers to a single byte and the other to an
instance of `struct enormous'?
 
B

Ben Bacarisse

gwowen said:
What nonsense.

The free conversion of void* predates that rationale by at least 10
years so that is, at best, a post-hoc justification.

That quote if from the rationale for 1989 standard, parts of which
were written before the final draft. Now "at least 10 years" takes us
back to 1979 -- one year after K&R was published. What C compilers
were using void * at that time?
Indeed, how
could "Keep the spirit of C" be a rationale in the design of C? That
rationale explains why standardization didn't change the behaviour of
void*, but it doesn't explain how that behaviour came to be.

Incidentally, are there any misfeatures of any language that could not
be rationalized by saying "we trust the programmer"?


Why, trust the programmer to specify it, of course.

"Trust the programmer" does not mean "burden the programmer with every
detail".
 

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

No members online now.

Forum statistics

Threads
474,008
Messages
2,570,268
Members
46,867
Latest member
Lonny Petersen

Latest Threads

Top