Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > would C be easier to read if...

Reply
Thread Tools

would C be easier to read if...

 
 
Hallvard B Furuseth
Guest
Posts: n/a
 
      04-04-2008
Ian Collins writes:
>Hallvard B Furuseth wrote:
>> The times where you do need to clutter the code with unreadable
>> type casts, it may make sense to typedef the offending type.
>>

> No, all the typedef dose is introduce an alias, it doesn't remove the
> need to cast an inappropriate type.


Indeed. But I was talking about making such code readable, which
is what this thread is about in the first place. Like you said
in your next message in this thread

> If you do need to clutter the code with unreadable casts, you are
> doing something smelly and should reconsider what you are doing.


Likely yes. Not always.

--
Hallvard
 
Reply With Quote
 
 
 
 
Owen Jacobson
Guest
Posts: n/a
 
      04-04-2008
On Apr 3, 6:04*pm, "Bartc" <(E-Mail Removed)> wrote:
> "Ben Pfaff" <(E-Mail Removed)> wrote in message
>
> news:(E-Mail Removed)...
>
> > "Bartc" <(E-Mail Removed)> writes:

>
> >> What on earth does (void *(*)(void*)) mean? It's some sort of cast, so
> >> the
> >> type is:

>
> >> void *(*)(void*)

>
> >> My guess is it's a function returning type void*, and maybe taking a
> >> single
> >> parameter of void*, but what about the (*) in the middle?!

>
> > That means it's a pointer to a function. *The full type is
> > "pointer to function taking a void * argument and returning void
> > *".

>
> OK thanks.
>
> >> And * does apparently seem to change position. Unless I've got these
> >> wrong:
> >> int *a * *a is pointer to int (* on left)

>
> > OK.

>
> >> (int *) * *pointer to int (* on right)

>
> > That's not a type or a declaration. *It's a cast. *In a cast,
> > there is no variable to name, so "a" is omitted. *If there was a
> > variable there, it would be in the same position.

>
> I agree that the type-declaration scheme /is/ logical when you analyse it,
> it's just difficult to read! Being adept at it does not change that.
>
> While we're in this fantasy thread, I knocked up this new left-to-right
> easy-to-read syntax for C type declarations. Type-decls are constructed with
> the following symbols (enums and some other stuff left out):
>
> * * * * * * * * Pointer to
> type * * * * * *Type name (built-in or typedef-ed)
> function * * * *Optional function indicator
> (...)T * * * * *Function Takes these params, returns type T
> [] * * * * * * *Array of
> [N] * * * * * * Array of N of
> struct{...} * * Struct of
> struct S{...} * Struct S of
>
> These are written left to right, example:
>
> *int a,b; * * * * * * * * a,b are both pointers to int
> function(int,int)float * *Function taking two int params and returning float
> result
> (int,int)float * * * * * *Same without the 'function'
> [10]char s,t * * * * * * *s,t are both 10-char arrays
> [10]*char list * * * * * *Array of pointers to char (array of strings)
> *function(*void)*void * * Above example
> *(*void)*void * * * * * * Above example without 'function'
>
> Pros:
> * Easy to read, translates naturally to English
> * Variables sharing same array/pointer properties share the one type-decl(as
> in above examples)
> * The typedef T U statement, in practice may have T and U hopelessly
> intermingled for complex type-decls. With this scheme T and U stay separate.
> * Also type-decls appear in typedefs, var-decls, casts, and function params
> all have the same form
>
> Cons:
> * Can't mix declaration of variables that have different array/pointer
> attributes (but I think that's a bad idea anyway).


Here's the thing: that's not any more or less readable than C's
syntax, just different. In order to really change the readability,
you'd have to go back to the type system itself and look at it.

C's type system admits the following[1] as types:

void (the type that has no values.)
char, short, int, long, long long, and unsigned/signed variants
(ordinal types.)
float and double
struct {T1, T2, ...} (structure/named tuple types.)
pointers to any function signature.
('a' is a type -> 'a (*) (T1, T2, ...)' is a type)
pointers to any value type
('a' is a type -> 'a *' is a type)
constants of any type
('a' is a type -> 'a const' is a type)

It also halfways admits to the existence of arrays; each pair
(unsigned number, type) is a distinct array type, but you can't pass
values of array types around directly.

Functions themselves, however, are not directly represented in C's
type system. This makes sense; C is not a functional language and you
cannot create functions in it at runtime.

The treatment of void is somewhat inconsistent: it's a type with
exactly zero values. All other types have at least one value.
Consequently, you can declare pointers to void, but not have
expressions or variables whose type is void.

I posit that any syntax that expresses the C type system will be
"messy" the same way C's syntax is: the mess is in the (pragmatic and
very useful) type system itself. If you want a language with a
cleaner syntax for expressing types, choose a language with a cleaner
type system.

-o-

[1] Not intended to be exhaustive.
 
Reply With Quote
 
 
 
 
vippstar@gmail.com
Guest
Posts: n/a
 
      04-04-2008
On Apr 3, 7:31 pm, Ben Bacarisse <(E-Mail Removed)> wrote:
> (E-Mail Removed) writes:
> > On Apr 3, 6:47 pm, Hallvard B Furuseth <(E-Mail Removed)>
> > wrote:
> >> (E-Mail Removed) writes:

> <snip>
> >> > (...) what I demonstrate here is a function taking void *, and
> >> > passing to that function a const char * const.

>
> >> void* isn't that magical. You can pass a char* to a void* or
> >> a const char* to a const void*. Not a const char* to a void*.

> > That is not true, and it's not about void *'s "magic". You can also
> > assign a const char * to a char * and still have a conforming
> > program.

>
> Only because "conforming program" is a rather loose term. A
> conforming program can use non-portable features.
>
> <snip>
>
> >> > (...) Here's a perfectly conforming C99 program (...)

>
> What do you mean by "perfectly conforming"? If you mean the same as
> "strictly conforming", I disagree. Here is the code again:
>
> #include <stdio.h>
> int foo(void *);
> int main(void) {
> const char * const p = "hello world";
> foo(p);
> return 0;}
>
> int foo(void *p) { return puts(p); }
>
> >> I'm not up to digging through the standard at the moment, but if you
> >> compile it with "gcc -std=c99 -pedantic-errors" and bugreport the
> >> resulting error message, maybe they'll tell you where to find it

> > gcc has idiotic and inaccurate warning messages [1] (which btw you
> > turn into diagnostic errors with -pedantic-errors)
> > My program is 100% conforming to C99 (and perhaps C89/POSIX too).

>
> Again, what is 100% conforming? It violates a constraint and thus
> requires a diagnostic.
>
> 6.5.2.2 Function calls
> Constraints
> ...
> 2 If the expression that denotes the called function has a type that
> includes a prototype, the number of arguments shall agree with the
> number of parameters. Each argument shall have a type such that its
> value may be assigned to an object with the unqualified version of
> the type of its corresponding parameter.
>
> I.e. the argument, p, must have a type such that its value may be
> assigned to an object of type void *. The correctness of the call is
> defined in terms of the correctness of:
>
> const char * const arg = "hello world";
> void *param = arg;
>
> So we got to:
>
> 6.5.16.1 Simple assignment
> Constraints
> - both operands are pointers to qualified or unqualified versions of
> compatible types, and the type pointed to by the left has all the
> qualifiers of the type pointed to by the right;
>

Oh, I'm such a fool.
My apologies to Hallvard B Furuseth..

(just when you think you finally know C...
<snip>
 
Reply With Quote
 
John Bode
Guest
Posts: n/a
 
      04-04-2008
On Apr 3, 1:23 pm, "Bartc" <(E-Mail Removed)> wrote:
> "Robert Smith" <(E-Mail Removed)> wrote in message
>
> news:rd5Jj.5608$(E-Mail Removed)...
>
> > some of the syntax wasn't overloaded so much...

>
> > Was just musing that if pointer de-referencing and pointer-to-type had
> > seperate syntax (ie use a character other than '*' for one of them) it
> > would make things much easier to read. You wouldn't get stuff like:

>
> > pthread_create(&thread, NULL, (void *(*)(void*))ThreadProc, (void
> > *)parameter);

>
> I've got more serious problems with understanding C declarations, for
> example:
>
> What on earth does (void *(*)(void*)) mean? It's some sort of cast, so the
> type is:
>
> void *(*)(void*)
>
> My guess is it's a function returning type void*, and maybe taking a single
> parameter of void*, but what about the (*) in the middle?!
>


That indicates that it's a pointer to a function.

A cast expression is basically a declaration without the identifier;
for example, when you see

(void *(*)(void*))ThreadProc

substitute

void *(*ThreadProc)(void*);

which reads as

ThreadProc -- ThreadProc
*ThreadProc -- is a pointer
(*ThreadProc)() -- to a function
(*ThreadProc)(void *) -- taking a void* parameter
*(*ThreadProc)(void *) -- and returning a pointer
void *(*ThreadProc)(void *) -- to void

> And * does apparently seem to change position. Unless I've got these wrong:
>
> int *a a is pointer to int (* on left)
> (int *) pointer to int (* on right)
> *a dereference pointer to int (* on left again).
>
> In my case a type declaration that reads linearly from left to right would
> help tremendously, because that's what I'm familiar with. Just having the
> word 'function' in a function declaration would make things so much clearer!
>
> Maybe C's syntax will get easier with use, I don't know, but since I stay
> well clear of anything complicated, probably not.
>


C's declaration syntax follows a "declaration mimics use" paradigm;
IOW, an object or function's declaration should look as much like how
it's referenced in the executable code as possible. This is probably
best explained with some examples.

Let's say we have a regular int identifier named "foo". Here's how it
would be referenced in the code:

foo = blah();
bar = foo;
printf("%d\n", foo);

So, the declaration of "foo" is pretty simple:

int foo;

Now let's pretend "foo" is a pointer to int. Using the same examples
as above:

*foo = blah();
bar = *foo;
printf("%d\n", *foo);

In each case above, we use the dereference operator "*" to get to the
integer value pointed to by foo. So, our declaration of foo is:

int *foo;

Now let's pretend "foo" is an array of pointers to int:

*foo[i] = blah();
bar = *foo[i];
printf("%d\n", *foo[i]);

And the declaration:

int *foo[N];

In the declarations above, "foo", "*foo", and "*foo[N]" are all called
declarators; the declarator introduces the name of the thing being
declared, and provides additional type information. In the
declaration

int *foo[N];

the int-ness of foo is provided by the type specifier "int", while the
pointer-ness and array-ness are provided by the "*" and "[]" in the
declarator. Note that the "*" and "[]" operators are bound to the
identifier, not the type specifier. Cast expressions seem to violate
this rule, but they really don't; again, think of the cast as a
declaration without an identifier, and mentally substitute the thing
being cast for where the identifier would go.

A few more examples:

int (*foo)[N]; // foo is a pointer to an array of int
...
(*foo)[i] = blah();
bar = (*foo)[i];
printf("%d\n", (*foo)[i]);

int (*foo)(void); // foo is a pointer to a function returning int
...
blah = (*foo)(); // can also be written simply as foo();
printf("%d\n", (*foo)());

int (*foo[N])(void); // foo is an N-element array of pointers to
// functions returning int
blah = (*foo[i])();
printf("%d\n", (*foo[i])());

When you see a particularly hairy declaration, like

char *(*(*(*foo)[N])(double bar))[M];

start by finding the leftmost identifier, and then work your way out,
remembering that [] and () bind before * (i.e., *x[] is an array of
pointer, (*x)[] is a pointer to an array, *x() is a function returning
a pointer, and (*x)() is a pointer to a function):

foo -- foo
*foo -- is a pointer
(*foo)[N] -- to an N-element array
*(*foo)[N] -- of pointers
(*(*foo)[N])() -- to functions
(*(*foo)[N])(double bar) -- taking a double
parameter
*(*(*foo)[N])(double bar) -- returning a pointer
(*(*(*foo)[N])(double bar))[M]; -- to an M-element array
*(*(*(*foo)[N])(double bar))[M]; -- of pointers
char *(*(*(*foo)[N])(double bar))[M]; -- to char

Alternately, you could go the other way, and work from the outside
in. Start by substituting everything between the outermost () with X:

char *X[M];

X is an M-element array of pointer to char.

Now we can start decomposing X:

X = (*Y)
char *(*Y)[M];

Y is a pointer to an M-element array of pointer to char

Y = Z(double bar)
char *(*Z(double bar))[M];

Z is a function returning a pointer to an M-element array of pointer
to char

Z = (*G)
char *(*(*G)(double bar))[M]

G is a pointer to a function returning a pointer to an M-element array
of pointer to char

G = H[N]
char *(*(*H[N])(double bar))[M]

H is an array of pointers to functions returning a pointer to an M-
element array of pointer to char

H = (*foo)
char *(*(*(*(*foo)[N])(double bar))[M]

foo is a pointer to an N-element array of pointers to functions
returning a pointer to an M-element array of pointer to char.
 
Reply With Quote
 
Ben Bacarisse
Guest
Posts: n/a
 
      04-04-2008
Owen Jacobson <(E-Mail Removed)> writes:
<snip simplifying C's types>
> Functions themselves, however, are not directly represented in C's
> type system.


This is not the case.

> This makes sense; C is not a functional language and you
> cannot create functions in it at runtime.


This is true, and it accounts for some of the limitations of function
types, but function types themselves *are* permitted in the type
system:

typedef int operation(int, int);

makes 'operation' a synonym for a function type. You can't use it
define any functions of this type (mainly, I assume, because you would
loose the parameter names), but you can declare functions using
it:

operation add, subtract, multiply, divide;

and you can define derived types from it:

typedef operation *bin_op_ptr;
operation *semantics[N_OPS];

It is about as good a type as C permits.

> I posit that any syntax that expresses the C type system will be
> "messy" the same way C's syntax is: the mess is in the (pragmatic and
> very useful) type system itself. If you want a language with a
> cleaner syntax for expressing types, choose a language with a cleaner
> type system.


The "mess" comes from using operator syntax that requires parentheses
for two different purposes to represent some types. E.g.

int *f(int); /* compare */ int (*f)(int);

Add to that the fact that ()s enclose a type when it is used as a cast
(or in a sizeof expression) and that the name can be omitted when it
is no needed and you get thing like (int (*)(int)) even for simple
types.

I've grown oddly fond of the syntax. I did not like it at first but
it has grown on me.

--
Ben.
 
Reply With Quote
 
Bartc
Guest
Posts: n/a
 
      04-04-2008
Owen Jacobson wrote:
> On Apr 3, 6:04 pm, "Bartc" <(E-Mail Removed)> wrote:
>> "Ben Pfaff" <(E-Mail Removed)> wrote in message
>>> "Bartc" <(E-Mail Removed)> writes:

>>
>>>> What on earth does (void *(*)(void*)) mean? It's some sort of


>>> That means it's a pointer to a function. The full type is
>>> "pointer to function taking a void * argument and returning void
>>> *".

>> I agree that the type-declaration scheme /is/ logical when you
>> analyse it, it's just difficult to read! Being adept at it does not
>> change that.
>>
>> While we're in this fantasy thread, I knocked up this new
>> left-to-right easy-to-read syntax for C type declarations.
>> Type-decls are constructed with the following symbols...
>>
>> * Pointer to
>> type Type name (built-in or typedef-ed)
>> function Optional function indicator
>> (...)T Function Takes these params, returns type T
>> [] Array of
>> [N] Array of N of
>> struct{...} Struct of
>> struct S{...} Struct S of
>>
>> These are written left to right, examples ...


>> *function(*void)*void Above example
>> *(*void)*void Above example without 'function'


> Here's the thing: that's not any more or less readable than C's
> syntax, just different.


Maybe, but it's certainly simpler. Expressed in English, type-declarations
tend to written linearly, from left to right, the same as my syntax.

C's syntax tends to start in the middle and work outwords, jumping from side
to side. And sometimes there's a name embedded in there, sometimes not. And
sometimes extra attributes like array of/pointer to are just added to
variable names. It's rather messy.

> In order to really change the readability,
> you'd have to go back to the type system itself and look at it.

.....
> I posit that any syntax that expresses the C type system will be
> "messy" the same way C's syntax is: the mess is in the (pragmatic and
> very useful) type system itself. If you want a language with a
> cleaner syntax for expressing types, choose a language with a cleaner
> type system.


There's nothing much wrong with the idea of the C type system; for this
level of language, it's fine.

My variation chose to use the same symbols but to rearrange them. They could
be modified further. Taking the original example, changing void* to int*, it
would be:

int *(*)(int*) Original C, but this changes in typedefs and named
declarations

*(*int)*int My variation without 'function'...
*function(*int)*int ... and with

"Pointer to Function taking Pointer to Int parameter and returning Pointer
to Int"
In English

ref function (ref int) => ref int
Algol-68-like with functional "=>" symbol for good
measure

Given that such a type is desired, there are only so many ways to express
it. And out of these, C chose the most convoluted and unintuitive. That is
unfortunate.

--
Bart


 
Reply With Quote
 
Hallvard B Furuseth
Guest
Posts: n/a
 
      04-04-2008
Bartc writes:
> C's syntax tends to start in the middle and work outwords, jumping
> from side to side. And sometimes there's a name embedded in there,
> sometimes not. And sometimes extra attributes like array of/pointer
> to are just added to variable names. It's rather messy.


That's why I'm saying "typedef".

> Given that such a type is desired, there are only so many ways to
> express it. And out of these, C chose the most convoluted and
> unintuitive. That is unfortunate.


Maybe. But if you change the declaration syntax that doesn't change the
expression syntax it reflects, so you still have to learn to dig through
that complex syntax. With a complicated but different declaration
syntax there would be one more syntax to learn.

The trick is to learn to read a declaration as in an expression: "If
this was an expression, how would it be evaluated?". That mostly
answers what the declaration is saying.

Similarly, a trick for reading casts like (int (*)(void *)): If the
contents of the outer () were a declaration, where is the syntax error?
I.e. where can you insert a variable name to make it a valid
declaration? Imagine a variable name there, then read it as a
declaration, and you know the type. (In this case, after the first *.)

--
Hallvard
 
Reply With Quote
 
Keith Thompson
Guest
Posts: n/a
 
      04-04-2008
Kenneth Brody <(E-Mail Removed)> writes:
> Robert Smith wrote:
>> some of the syntax wasn't overloaded so much...
>>
>> Was just musing that if pointer de-referencing and pointer-to-type had
>> seperate syntax (ie use a character other than '*' for one of them) it would
>> make things much easier to read. You wouldn't get stuff like:
>>
>> pthread_create(&thread, NULL, (void *(*)(void*))ThreadProc, (void
>> *)parameter);

>
> Aside from what the others said, consider this:
>
> char *p;
>
> The use of "*" is consistent, IMO, because:
>
> "p" is of type "char *", just like the definition says, and
> "*p" is of type "char", just like the definition says.


Be careful. Your second statement, that *p is of type char, is
correct and consistent with the syntax of the declaration. The first,
that p is of type char*, is also correct, but it's not really what the
declaration *means*, at least not directly.

Consider:

char *p, q;

This says that *p is of type char (implying that p is of type char*),
but q is of type char, so p and q are of different types.

There's a school of thought that says that, in a pointer declaration,
the "*" should be adjacent to the type:

char* p;

and the declaration should be read as "p is of type char*". The above
problem is then avoided by declaring only one object per line:

char* p;
char q;

Personally, I prefer to have the code layout reflect the syntax. As a
programmer, you have to understand the syntax anyway, so I see no
point in concealing it with odd spacing.

Kenneth, I don't mean to imply that you don't understand this; my
remarks are mostly just a matter of emphasis.

--
Keith Thompson (The_Other_Keith) <(E-Mail Removed)>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
an oddball scary kind of thing you would think would never happen richard Computer Support 4 01-31-2010 06:34 PM
read a ruby script like you would read a text file Mmcolli00 Mom Ruby 2 01-27-2009 10:52 PM
Has to be an easier way? (gateway issues) Captain Cisco 13 08-28-2004 12:30 AM
Hash of Structs in a Package, is there an easier way? Norman Ackroyd Perl 1 07-28-2004 11:54 AM
Phone Validation Problem (I reformatted the code to make it easier to read) AnnMarie Javascript 8 11-21-2003 10:37 PM



Advertisments