would C be easier to read if...

R

Robert Smith

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);
 
C

Chris Dollin

Robert said:
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);

Surely either `ThreadProc` is already the right type, in which case
the cast can and should be discarded, or it isn't, in which case
the cast is a bug waiting to manifest and `ThreadProc` should be
fixed.

Yes?
 
H

Hallvard B Furuseth

Chris said:
Surely either `ThreadProc` is already the right type, in which case
the cast can and should be discarded, or it isn't, in which case
the cast is a bug waiting to manifest and `ThreadProc` should be
fixed.

Yes?

And hopefully 'parameter' is a non-const pointer so the void* cast
can be dropped too.

The times where you do need to clutter the code with unreadable
type casts, it may make sense to typedef the offending type.
 
B

Ben Bacarisse

Robert Smith said:
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);

All the *s there have the same meaning, don't they? How would
changing the symbol help?
 
V

vippstar

C is old. Back then a lot of symbols were missing from the keyboard,
thus C has a simple character set. (and alternatives such as digraphs
and trigraphs which are not that used, but some regulars claim to have
used them under certain circumstances)
I don't think adding/changing symbols would make the language simpler.
And hopefully 'parameter' is a non-const pointer so the void* cast
can be dropped too.
Even if parameter was const char * const parameter = "hello world";
the cast can still be omitted, as long as ThreadProc does not try to
mutate what is pointed to by parameter.
 
H

Hallvard B Furuseth

Robert said:
some of the syntax wasn't overloaded so much...

Overloaded symbols are a problem in C, yes. But in this case it's a
design feature that syntax for declaring a variable is the same as for
using it. As a result there is one and not two syntaxes you need to
learn in order to dig your way through a complex use of a variable.

Even if parameter was const char * const parameter = "hello world";
the cast can still be omitted, as long as ThreadProc does not try to
mutate what is pointed to by parameter.

No. pthread_create() takes a void* 3rd parameter. It's an error
to assign or pass a "pointer to const" to a "pointer to not-const".
On the other hand the const may be cast away, and if the underlying
object was not created with 'const' type it may be mutated.
 
V

vippstar

Overloaded symbols are a problem in C, yes. But in this case it's a
design feature that syntax for declaring a variable is the same as for
using it. As a result there is one and not two syntaxes you need to
learn in order to dig your way through a complex use of a variable.




No. pthread_create() takes a void* 3rd parameter. It's an error
to assign or pass a "pointer to const" to a "pointer to not-const".
I don't remember what the POSIX standard says about this, but in C it
is not. Ofcourse, pthread_create is not mentioned in the C99 standard,
however, what I demonstrate here is a function taking void *, and
passing to that function a const char * const. Note that this function
could be the callback to pthread_create. In pthread_create()'s manual
there is no mention of *requiring* the last argument to be modifiable.

Here's a perfectly conforming C99 program

#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); }
 
H

Hallvard B Furuseth

I don't remember what the POSIX standard says about this, but in C it
is not.

You are confusing something with something. Half the purpose of the
const qualifier is that the compiler's type checking can help you catch
assignments to read-only objects.
(...) 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*.
(...) Here's a perfectly conforming C99 program (...)

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:)
 
V

vippstar

You are confusing something with something. Half the purpose of the
const qualifier is that the compiler's type checking can help you catch
assignments to read-only objects.
Well, that's *your* opinion. My opinion is that const is there for
optimization and documentation.
Ie foo(const int *) informs the programmer foo shall not modify what
is pointed to by its argument.
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.
Also, while thinking about it, pthread_create shouldn't be able to
access what is pointed to by its last argument. Unless POSIX says
something about it (and I'm positive it does not) then it's POSIX
conforming too.
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).

[1] It correctly warns/errors, however the semantics are incorrect, eg
says "statement" where "expression" should be used, et cetera.
 
B

Ben Bacarisse

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.


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
One of the following shall hold:

— the left operand has qualified or unqualified arithmetic type and
the right has arithmetic type;

— the left operand has a qualified or unqualified version of a
structure or union type compatible with the type of the right;

— 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;

No to these three.

— one operand is a pointer to an object or incomplete type and the
other is a pointer to a qualified or unqualified version of void,
and the type pointed to by the left has all the qualifiers of the
type pointed to by the right;

No, but this is close. One (arg) is a pointer to an object and the
other (param) is a pointer to an unqualified version of void, but the
type pointed to by the left (void) does not have all the qualifiers of
the type pointed to by the right (const char).

— the left operand is a pointer and the right is a null pointer
constant; or

— the left operand has type _Bool and the right is a pointer.

No for these as well. So gcc is correct in issuing a diagnostic.
 
B

Bartc

Robert Smith said:
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?!

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.
 
B

Ben Pfaff

Bartc said:
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
*".
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.

There is one situation where the variable name may be included or
omitted, at your option, and that is in a function prototype (that
is not for a function being defined). So the following are
equivalent, and may be enlightening:
void foo(int *a);
void foo(int *);
See? The variable name is just omitted, and there's no changing
of position going on.
 
I

Ian Collins

Well, that's *your* opinion. My opinion is that const is there for
optimization and documentation.

Well most compilers would agree with Hallvard and not with you.

Passing a const pointer to a function with a void* parameter is a
constraint violation and the compiler should issue a diagnostic.
Ie foo(const int *) informs the programmer foo shall not modify what
is pointed to by its argument.

True, and foo( int* ) offers no such guarantee, so passing a const int*
to foo risks foo attempting to modify the data pointer to.
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.

No, you have a constraint violation.
Also, while thinking about it, pthread_create shouldn't be able to
access what is pointed to by its last argument. Unless POSIX says
something about it (and I'm positive it does not) then it's POSIX
conforming too.
Nonsense, what's the point in a parameter a function can't access? Even
is there was one, how could it be enforced?
 
I

Ian Collins

Hallvard said:
And hopefully 'parameter' is a non-const pointer so the void* cast
can be dropped too.

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.

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

Ian Collins

Robert said:
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);
It has already been pointed out that the casts are a smell, but even if
they were required, C provides typedefs to alias complex type declarations.

It's a shame typedefs aren't used more often for function pointer types
in standards. It would make complex function declarations (signal is a
classic) much easier for a human to parse.
 
K

Kenneth Brody

Robert said:
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.

--
+-------------------------+--------------------+-----------------------+
| Kenneth J. Brody | www.hvcomputer.com | #include |
| kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------+
Don't e-mail me at: <mailto:[email protected]>
 
C

CBFalconer

.... snip ...

Here's a perfectly conforming C99 program

#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); }

And here is one with one less line, no requirements for exact line
equivalence, and much more understandable, especially to the
neophyte.

#include <stdio.h>

int foo(void *p) { return puts(p); }

int main(void) {
char *p = "hello world";

foo(p);
return 0;
}

I also added blank lines to separate code segments. I didn't
install 'static' qualifiers.

I thought I had eliminated all possible warning, but I got:

junk.c:6: warning: initialization discards qualifiers from pointer
target type

and I am confused. Why? gcc is run through an alias for cc:

alias cc=gcc -W -Wall -ansi -pedantic -Wwrite-strings -Wfloat-equal
-gstabs+ -ftrapv -O1

(I suspect the -Wwrite-strings param).
 
B

Bartc

Ben Pfaff said:
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.
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).
 
C

christian.bau

Well, that's *your* opinion. My opinion is that const is there for
optimization and documentation.

"Const" cannot be used for optimization purposes at all. Your code
either modifies objects, or it doesn't. The compiler doesn't need
const qualifiers to see that. But a const qualifier on a pointer does
_not_ in any way whatsoever guarantee or indicate the the object is
not modified in some other way.

The situation is completely different if you use a "const restrict"
pointer in C99.
 
D

Default User

CBFalconer said:
And here is one with one less line, no requirements for exact line
equivalence, and much more understandable, especially to the
neophyte.

#include <stdio.h>

int foo(void *p) { return puts(p); }

int main(void) {
char *p = "hello world";

foo(p);
return 0;
}

I also added blank lines to separate code segments. I didn't
install 'static' qualifiers.

I thought I had eliminated all possible warning, but I got:

junk.c:6: warning: initialization discards qualifiers from pointer
target type

and I am confused. Why? gcc is run through an alias for cc:

alias cc=gcc -W -Wall -ansi -pedantic -Wwrite-strings -Wfloat-equal
-gstabs+ -ftrapv -O1

(I suspect the -Wwrite-strings param).

I suspect you are right.

-Wwrite-strings
When compiling C, give string constants the type "const
char[length]" so that copying the address of one into a
non-"const" "char *" pointer will get a warning; when
compiling C++, warn about the deprecated conversion from
string constants to "char *". These warnings will help
you find at compile time code that can try to write into
a string constant, but only if you have been very
careful about using "const" in declarations and
prototypes. Otherwise, it will just be a nuisance; this
is why we did not make -Wall request these warnings.



Brian
 

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
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top