Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Proposal to remove forward declaration requirements

878 views
Skip to first unread message

Rick C. Hodgin

unread,
Nov 10, 2015, 5:27:49 PM11/10/15
to
I'd like to propose that the next standard include a relaxation which
allows the need for forward declarations to be removed, to be replaced
with an option allowing a multi-pass compilation to resolve declarations
unknown until subsequent source code is processed.

Thank you for considering this feature which should've been included
in the C71 standard.

Best regards,
Rick C. Hodgin

Jakob Bohm

unread,
Nov 10, 2015, 9:41:30 PM11/10/15
to
On 10/11/2015 23:27, Rick C. Hodgin wrote:
> I'd like to propose that the next standard include a relaxation which
> allows the need for forward declarations to be removed, to be replaced
> with an option allowing a multi-pass compilation to resolve declarations
> unknown until subsequent source code is processed.
>

Please don't (for multiple reasons).


Enjoy

Jakob
--
Jakob Bohm, CIO, Partner, WiseMo A/S. https://www.wisemo.com
Transformervej 29, 2860 Søborg, Denmark. Direct +45 31 13 16 10
This public discussion message is non-binding and may contain errors.
WiseMo - Remote Service Management for PCs, Phones and Embedded

Kaz Kylheku

unread,
Nov 10, 2015, 9:44:43 PM11/10/15
to
There was no C in 1971. The C from 1975 or thereabouts had such a feature,
at least for functions:

main()
{
int x = never_seen_before();
/* x gets 42 */
}

/* later in the same file, or in a different one */
never_seen_before()
{
return 42;
}

This is deprecated now.

Keith Thompson

unread,
Nov 10, 2015, 10:19:11 PM11/10/15
to
It's not merely deprecated. Calling an undeclared function has been
illegal (a constraint violation) since C99.

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Rick C. Hodgin

unread,
Nov 11, 2015, 12:10:39 AM11/11/15
to
Kaz Klyheku wrote:
> There was no C in 1971.

C was released in 1972. My point was that it should
have been there in drafts and discussions before
it was ever released. I.e. from day one following
the, "You know what we should do?" moment
where discussions about writing C began.

jacob navia

unread,
Nov 11, 2015, 7:26:30 AM11/11/15
to
Suppose this:

someFn("abc");

No declarations of "someFn" anywhere in the compilation unit since it is
defined somewhere else, i.e. in another source file.

That would be legal?

In that case we have to give up compilation by modules and compile all
the source files at each time since we can never know when something has
changed in module A that affects modules B and C that need recompilation...

Besides that small problem, the reader of the code has NO CLUE as to
where in the source code the definition can be... Just start grepping
around, filter the tons of lines to find the definition...


Rick C. Hodgin

unread,
Nov 11, 2015, 7:54:25 AM11/11/15
to
On Wednesday, November 11, 2015 at 7:26:30 AM UTC-5, jacob navia wrote:
> Le 10/11/2015 23:27, Rick C. Hodgin a écrit :
> > I'd like to propose that the next standard include a relaxation which
> > allows the need for forward declarations to be removed, to be replaced
> > with an option allowing a multi-pass compilation to resolve declarations
> > unknown until subsequent source code is processed.
> >
> > Thank you for considering this feature which should've been included
> > in the C71 standard.
> >
> > Best regards,
> > Rick C. Hodgin
> >
>
> Suppose this:
>
> someFn("abc");
>
> No declarations of "someFn" anywhere in the compilation unit since it is
> defined somewhere else, i.e. in another source file.
>
> That would be legal?

Under the proposed relaxation, it would not be legal. The use of
anything in a source file would be required to be known to the source
or one of its include files, either through forward declarations, or
to be found at a point later in the source file than where it's first
referenced.

Note also that this is a relaxation of the REQUIREMENT of having
forward declarations. It is not a proposal to remove them completely.
Only that in many cases they are not needed.

> In that case we have to give up compilation by modules and compile all
> the source files at each time since we can never know when something has
> changed in module A that affects modules B and C that need recompilation...

I do not propose that change. I only that on source files like this:

int main(int argc, char* argv[])
{
printf("%s\n", my_function());
exit(0);
}

char* my_function(void)
{
return("Hello, world!\n");
}

...that I don't need to forward-declare my_function() because it's
right there in the source file, just declared later on. I'm fully
prepared to take the mild performance hit in compilation time to
gain such a feature. And I think every other C developer out there
is as well because such a feature would ONLY be required when there
were unknowns found in the first pass, which must be resolved in a
second pass. And in the case of legitimate unknowns (something's
wrong with the source file, something's missing) then it will result
in an error which will terminate the compilation anyway. And in the
case of illegitimate unknowns (it is known later in the file, just
not at the point of first use), then a mild performance hit to parse
back through the second pass on those lines... it doesn't seem like
too much to bear, or inquire for.

> Besides that small problem, the reader of the code has NO CLUE as to
> where in the source code the definition can be... Just start grepping
> around, filter the tons of lines to find the definition...

Knowledge of the system design a developer works on is crucial to
achieving success in development. When that knowledge is not present,
grepping around is the appropriate action.

martin...@yahoo.co.uk

unread,
Nov 11, 2015, 7:56:25 AM11/11/15
to
On Wednesday, 11 November 2015 13:26:30 UTC+1, jacob navia wrote:
> Le 10/11/2015 23:27, Rick C. Hodgin a écrit :
> > I'd like to propose that the next standard include a relaxation which
> > allows the need for forward declarations to be removed, to be replaced
> > with an option allowing a multi-pass compilation to resolve declarations
> > unknown until subsequent source code is processed.
> >
> Suppose this:
>
> someFn("abc");
>
> No declarations of "someFn" anywhere in the compilation unit since it is
> defined somewhere else, i.e. in another source file.
>
> That would be legal?

If I understand the OP correctly: No, that would not be legal.

What would be legal, is call somefn at one point in the compilation
unit, and then at some later point *in the same compilation unit*
define somefn.

That would allow people to write the external functions first,
calling the local static functions which are defined lower down the
file. I quite often end up defining the local static functions first
because I'm too lazy to write out separate declarations.

It is clearly *possible* (C++ manages something similar in the definition
of inline member functions - they can use other members that are declared
lower down). I really can't see much demand for it though.

Kaz Kylheku

unread,
Nov 11, 2015, 11:01:56 AM11/11/15
to
On 2015-11-11, Rick C. Hodgin <rick.c...@gmail.com> wrote:
> Kaz Klyheku wrote:
>> There was no C in 1971.
>
> C was released in 1972.

I misremembered the dates a bit; so I went back to Dennis Ritchie's paper,
"Development of C" [https://www.bell-labs.com/usr/dmr/www/chist.pdf]:

Around 1971, it was still the B language, I think:

"In 1971 I began to extend the B language by adding a character type and
also rewrote its compiler to generate PDP-11 machine instructions instead
of threaded code. Thus the transition from B to C was contemporaneous with
the creation of a compiler capable of producing programs fast and small
enough to compete with assembly language. I called the slightly-extended
language NB, for ‘new B.’"

[ ... ]

"By early 1973, the essentials of modern C were complete."

[ ... ]

"During 1973-1980, the language grew a bit: the type structure gained
unsigned, long, union, and enumeration types, and structures became nearly
first-class objects (lacking only a notation for literals)."

Rick C. Hodgin

unread,
Nov 11, 2015, 11:04:45 AM11/11/15
to
Yes. In 1971, as he was extending B to pre-C, he should've been
considering not having forward declaration requirements, but only
the ability to possess them. They are often useful, but should not
always be required.

Kaz Kylheku

unread,
Nov 11, 2015, 11:10:46 AM11/11/15
to
On 2015-11-11, jacob navia <ja...@jacob.remcomp.fr> wrote:
> Le 10/11/2015 23:27, Rick C. Hodgin a écrit :
>> I'd like to propose that the next standard include a relaxation which
>> allows the need for forward declarations to be removed, to be replaced
>> with an option allowing a multi-pass compilation to resolve declarations
>> unknown until subsequent source code is processed.
>>
>> Thank you for considering this feature which should've been included
>> in the C71 standard.
>>
>> Best regards,
>> Rick C. Hodgin
>>
>
> Suppose this:
>
> someFn("abc");
>
> No declarations of "someFn" anywhere in the compilation unit since it is
> defined somewhere else, i.e. in another source file.
>
> That would be legal?

It is valid in the 1990 dialect of ISO C, its predecessors, and
related historic dialects.

> In that case we have to give up compilation by modules and compile all
> the source files at each time since we can never know when something has

Yet in those languages, it wasn't the case.

> changed in module A that affects modules B and C that need recompilation...

someFn("abc") implies that there exists somewhere a function
"int someFn(char *)" or perhaps "int someFn(const char *)".

If this isn't the case, recompiling the caller will not help, because
the caller doesn't have a matching declaration; it will still be incorrect
after recompilation.

If it *is* the case that someFn is defined in the expected way, but changes are
made to someFn which alter its *behavior*, there is no need to recompile
the caller; no aspect of the compile-time dependency has changed.

It's exactly the same situation as if we had a "someFn.h" header file
declaring the function, but made a change which only affects "someFn.c"
and not "someFn.h". In that case, only "someFn.c" has to be recompiled.

The problem with implicit declaration is that if we change the *interface*
of someFn, we do not get a diagnostic for all the places which are calling it
wrong.

> Besides that small problem, the reader of the code has NO CLUE as to
> where in the source code the definition can be... Just start grepping
> around, filter the tons of lines to find the definition...

Mandatory declarations do not help with this issue in any way.

Kaz Kylheku

unread,
Nov 11, 2015, 11:12:36 AM11/11/15
to
On 2015-11-11, martin...@yahoo.co.uk <martin...@yahoo.co.uk> wrote:
> If I understand the OP correctly: No, that would not be legal.
>
> What would be legal, is call somefn at one point in the compilation
> unit, and then at some later point *in the same compilation unit*
> define somefn.
>
> That would allow people to write the external functions first,
> calling the local static functions which are defined lower down the
> file.

Anything which encourages this awful style of arranging a program, sets
me dead against itself as a matter of a knee-jerk reflex. :)

Rick C. Hodgin

unread,
Nov 11, 2015, 11:23:04 AM11/11/15
to
Which awful style is that, Kaz?

// No forward declaration requirement:
main(); // Calls support1();
support1(); // Calls support2();
support2(); // No other calls

Instead of:

// No forward declaration requirement
support2(); // No other calls
support1(); // Calls support2();
main(); // Calls support1();

Why would anyone want to write code backwards?

Or:

// Forward declaration for support1();
// Forward declaration for support2();
main(); // Calls support1();
support1(); // Calls support2();
support2(); // No other calls

So now I've added unnecessary repetition and redundancy, coupled to
the need to keep two things in sync while I'm editing and maintaining
my source code.

?? Better ?? No, sir. It's catering to 1970s hardware, and not to
2015s hardware. It's catering to the requirements of lazy compiler
writers, and not to the needs of the many non-compiler-writer living,
breathing human beings who must also write C code in their lives.

Our goals should be to meet people's needs, not machine's needs, when
there is no machine need which supersedes people needs.

Kaz Kylheku

unread,
Nov 11, 2015, 12:18:16 PM11/11/15
to
On 2015-11-11, Rick C. Hodgin <rick.c...@gmail.com> wrote:
> On Wednesday, November 11, 2015 at 11:12:36 AM UTC-5, Kaz Kylheku wrote:
>> On 2015-11-11, martin...@yahoo.co.uk <martin...@yahoo.co.uk> wrote:
>> > If I understand the OP correctly: No, that would not be legal.
>> >
>> > What would be legal, is call somefn at one point in the compilation
>> > unit, and then at some later point *in the same compilation unit*
>> > define somefn.
>> >
>> > That would allow people to write the external functions first,
>> > calling the local static functions which are defined lower down the
>> > file.
>>
>> Anything which encourages this awful style of arranging a program, sets
>> me dead against itself as a matter of a knee-jerk reflex. :)
>
> Which awful style is that, Kaz?

The one where you call a something before defining it.

It wouldn't be as awful though if it were *enforced* as the one way to
do it. What we don't want is to support an ad-hoc mess where the definition
can be hiding anywhere among the uses, so that when you're considering a
particular use, you don't know in which direction the definition lies.

> // No forward declaration requirement:
> main(); // Calls support1();
> support1(); // Calls support2();
> support2(); // No other calls

This will devolve over time, e.g:

support3() {...} // calls support2();
main(); // Calls support1();
support1() {...} // Calls support3();
support2() {...}

Unfortunately this tends to happen in translation units with many public
functions, which are also used internally. The header file supplies
all the declarations beforehand, and so things get ... out of hand.

From time to time, it is good to do some housekeeping to put the functions
in order; on the other hand, that introduces diffs into the version history
that can cause gratuitous merge conflicts.

> Instead of:
>
> // No forward declaration requirement
> support2(); // No other calls
> support1(); // Calls support2();
> main(); // Calls support1();

This latter one is the best way to write computer programs
in any language. Low level routines first, then top.

It follows how we design systems, and how our best minds think.

The language doesn't require you to write any forward declarations
if you follow this way --- except, of course, when you introduce nontrivial
loops into the call graph with mutual recursion. That's what forward
declarations are for.

Ideally, the presence of declarations in an impelmentation file signals to the
reader "recursion among functions is going on here".

Rick C. Hodgin

unread,
Nov 11, 2015, 12:38:17 PM11/11/15
to
No. I encounter your position and that explanation often in computer
programming, but it is in no way universe. It is also not how our minds
think. We think in broad strokes first, then details. We look to
understand the big picture, then if we NEED to, only then do we go down
into details.

> The language doesn't require you to write any forward declarations
> if you follow this way --- except, of course, when you introduce nontrivial
> loops into the call graph with mutual recursion. That's what forward
> declarations are for.
>
> Ideally, the presence of declarations in an impelmentation file signals to the
> reader "recursion among functions is going on here".

Rick C. Hodgin

unread,
Nov 11, 2015, 12:40:49 PM11/11/15
to
On Wednesday, November 11, 2015 at 12:38:17 PM UTC-5, Rick C. Hodgin wrote:
> On Wednesday, November 11, 2015 at 12:18:16 PM UTC-5, Kaz Kylheku wrote:
> > This latter one is the best way to write computer programs
> > in any language. Low level routines first, then top.
> >
> > It follows how we design systems, and how our best minds think.
>
> No. I encounter your position and that explanation often in computer
> programming, but it is in no way universe. It is also not how our minds

Should be "it is in no way universal."

> think. We think in broad strokes first, then details. We look to
> understand the big picture, then if we NEED to, only then do we go down
> into details.

Kaz Kylheku

unread,
Nov 11, 2015, 1:31:39 PM11/11/15
to
On 2015-11-11, Rick C. Hodgin <rick.c...@gmail.com> wrote:
> On Wednesday, November 11, 2015 at 12:18:16 PM UTC-5, Kaz Kylheku wrote:
>> This latter one is the best way to write computer programs
>> in any language. Low level routines first, then top.
>>
>> It follows how we design systems, and how our best minds think.
>
> No. I encounter your position and that explanation often in computer
> programming, but it is in no way universe. It is also not how our minds
> think. We think in broad strokes first, then details. We look to
> understand the big picture, then if we NEED to, only then do we go down
> into details.

Ken Thompson said this in an interview once:

[Interviewer:] Your nominators and endorsers for the Kanai Award consistently
characterized your work as simple yet powerful. How do you discover such
powerful abstractions?

Thompson: It is the way I think. I am a very bottom-up thinker. If you give
me the right kind of Tinker Toys, I can imagine the building. I can sit there
and see primitives and recognize their power to build structures a half mile
high, if only I had just one more to make it functionally complete. I can see
those kinds of things.

The converse is true, too, I think. I can't from the building imagine the
Tinker Toys. When I see a top-down description of a system or language that
has infinite libraries described by layers and layers, all I just see is a
morass. I can't get a feel for it. I can't understand how the pieces fit; I
can't understand something presented to me that's very complex. Maybe I do
what I do because if I built anything more complicated, I couldn't understand
it. I really must break it down into little pieces.

[Unix and Beyond: an Interview with Ken Thompson]

Rick C. Hodgin

unread,
Nov 11, 2015, 2:07:37 PM11/11/15
to
Tinker Toys to buildings is one thing. Software details to higher
level constructs is another, unless you're talking about, as I presume
Thompson is here, the fundamental abilities which carry out very
specific workloads, such as the various unix script sand commands.

But jumping into a new project and trying to look at the renderPixel()
function without understanding its contextual usage is an issue. It
will not take on any meaning if you don't know why it exists.

Rick C. Hodgin

unread,
Nov 11, 2015, 2:10:53 PM11/11/15
to
BTW, I think Thompson changed later in life:

-----
From Google's "Go" Language FAQ:

https://golang.org/doc/faq

Q: "What are the guiding principles in the [Go] design?

A: "Programming today involves too much bookkeeping, repetition,
and clerical work. As Dick Gabriel says, ''Old programs read like
quiet conversations between a well-spoken research worker and a
well-studied mechanical colleague, not as a debate with a compiler.
Who'd have guessed sophistication bought such noise?'' The
sophistication is worthwhile -- no one wants to go back to the old
languages -- but can it be more quietly achieved?

"Go attempts to reduce the amount of typing in both senses of the
word. Throughout its design, we have tried to reduce clutter and
complexity. There are no forward declarations and no header files;
everything is declared exactly once. Initialization is expressive,
automatic, and easy to use. Syntax is clean and light on keywords."

-----
"Everything is declared exactly once."

Ken Thompson is one of three original authors of the Go Language:
https://en.wikipedia.org/wiki/Go_%28programming_language%29

They took ideas from C, but them migrated them to make more sense
to developers. One of the first things to go was the manual need
for headers and forward declarations.

Wojtek Lerch

unread,
Nov 12, 2015, 12:03:57 AM11/12/15
to
On 11/11/2015 7:54 AM, Rick C. Hodgin wrote:
...
> I do not propose that change. I only that on source files like this:
...
> printf("%s\n", my_function());
...
> char* my_function(void)

What about things that are not functions?

int main( void ) {
next = getfoo();
var.next = next;
struct foo *next;
}

struct foo *getfoo( void ) {
return malloc( sizeof(struct foo) );
}

struct foo var;

struct foo {
struct foo *next;
};

Rick C. Hodgin

unread,
Nov 12, 2015, 5:07:11 AM11/12/15
to
Completely acceptable. The compiler would be able to resolve each as:

int main( void ) {
next = getfoo(); // <==== var is local variable, known second pass
// getfoo() is struct foo* function, known
// also on second pass.

var.next = next; // <==== next is local variable, known on second
// pass

struct foo *next; // <==== local variable definition, known on
// second pass
}

struct foo *getfoo( void ) { // <==== declaration is legal on
// first pass because it's a ptr
return malloc( sizeof(struct foo) ); // <==== sizeof() unknown
// on first pass,
// resolvable on
// second pass
}

struct foo var; // <==== Global variable, resolvable on second pass

struct foo {
struct foo *next;
};

All perfectly fine to the compiler relaxation I propose. The things
unknown would just not be known until the second pass when it went
back through to re-process anything that was unknown on the first
pass, only then reporting unknown errors if at some point above a
reference to strut foo2* was found, but no foo2 definition existed,
for example.

jacob navia

unread,
Nov 12, 2015, 6:37:10 AM11/12/15
to
Le 11/11/2015 17:10, Kaz Kylheku a écrit :
> On 2015-11-11, jacob navia <ja...@jacob.remcomp.fr> wrote:
>> Le 10/11/2015 23:27, Rick C. Hodgin a écrit :
>>> I'd like to propose that the next standard include a relaxation which
>>> allows the need for forward declarations to be removed, to be replaced
>>> with an option allowing a multi-pass compilation to resolve declarations
>>> unknown until subsequent source code is processed.
>>>
>>> Thank you for considering this feature which should've been included
>>> in the C71 standard.
>>>
>>> Best regards,
>>> Rick C. Hodgin
>>>
>>
>> Suppose this:
>>
>> someFn("abc");
>>
>> No declarations of "someFn" anywhere in the compilation unit since it is
>> defined somewhere else, i.e. in another source file.
>>
>> That would be legal?
>
> It is valid in the 1990 dialect of ISO C, its predecessors, and
> related historic dialects.
>

No. What was legal in K&R C was that any not declared function would be
supposed to return an int.

That means that when that function returned double the compiler would
generate incorrect code. I do not know of any other language besides C
that did something like this.

>> In that case we have to give up compilation by modules and compile all
>> the source files at each time since we can never know when something has
>
> Yet in those languages, it wasn't the case.
>


Yes. That wasn't the case and incorrect code was generated and you had
no clue as to why your code wasn't working unless you started reviewing
ALL the code.

>> changed in module A that affects modules B and C that need recompilation...
>
> someFn("abc") implies that there exists somewhere a function
> "int someFn(char *)" or perhaps "int someFn(const char *)".
>

Yes. And if that is not true you are doomed.

[snip]

>> Besides that small problem, the reader of the code has NO CLUE as to
>> where in the source code the definition can be... Just start grepping
>> around, filter the tons of lines to find the definition...
>
> Mandatory declarations do not help with this issue in any way.
>

Yes. You generate the cpp output file and you grep. The FIRST line of
the grep result is the declaration.

Kaz Kylheku

unread,
Nov 12, 2015, 9:36:20 AM11/12/15
to
On 2015-11-12, Rick C. Hodgin <rick.c...@gmail.com> wrote:
> On Thursday, November 12, 2015 at 12:03:57 AM UTC-5, Wojtek Lerch wrote:
>> On 11/11/2015 7:54 AM, Rick C. Hodgin wrote:
>> ...
>> > I do not propose that change. I only that on source files like this:
>> ...
>> > printf("%s\n", my_function());
>> ...
>> > char* my_function(void)
>>
>> What about things that are not functions?
>>
>> int main( void ) {
>> next = getfoo();
>> var.next = next;
>> struct foo *next;
>> }
>>
>> struct foo *getfoo( void ) {
>> return malloc( sizeof(struct foo) );
>> }
>>
>> struct foo var;
>>
>> struct foo {
>> struct foo *next;
>> };
>
> Completely acceptable. The compiler would be able to resolve each as:
>
> int main( void ) {
> next = getfoo(); // <==== var is local variable, known second pass
> // getfoo() is struct foo* function, known
> // also on second pass.

Thus if you misspell the name of a variable in an assignment, you automatically
get a declaration for a new variable, where the assignment has become an
initialization:

struct foo *next = 0;
// ...
// ...
nxt = getfoo(); // Oops! Turns into "struct foo *nxt = getfoo();"

Rick C. Hodgin

unread,
Nov 12, 2015, 9:39:47 AM11/12/15
to
I don't understand what you mean here. I see this as an error the
compiler would generate regarding not nxt being unknown. How would
this statement define "nxt = getfoo();" to be
"struct foo* nxt = getfoo();" ?

Rick C. Hodgin

unread,
Nov 12, 2015, 9:46:24 AM11/12/15
to
Oh dyslexia, dyslexia, how fare thou art... thy light shines upon me
as the noonday sun, casting forth thy presence upon me without end...

Should be "regarding nxt being unknown" ... delete the "not". :-)

Rick C. Hodgin

unread,
Nov 12, 2015, 9:47:32 AM11/12/15
to
Ugh! "Fair" even. :-) Wow! I'm in rare form today.

Wojtek Lerch

unread,
Nov 12, 2015, 10:52:19 AM11/12/15
to
On 12-Nov-15 5:07 AM, Rick C. Hodgin wrote:
> On Thursday, November 12, 2015 at 12:03:57 AM UTC-5, Wojtek Lerch wrote:
...
>> What about things that are not functions?
...
> Completely acceptable.
...

What about this then:

int var;

void foo( void ) {
var = 6; // Does this assign to the global var or the local var?
int var;
...
}


Rick C. Hodgin

unread,
Nov 12, 2015, 11:18:54 AM11/12/15
to
Local var. When the relaxation is in effect, the compiler would not be
able to compile the "var = 6;" line until the first pass is completed,
so that all variables in all scopes are found.

Kaz Kylheku

unread,
Nov 12, 2015, 1:35:17 PM11/12/15
to
Exactly the same way as in your proposal, quoted above, which I repeat:

> int main( void ) {
> next = getfoo(); // <==== var is local variable, known second pass
> // getfoo() is struct foo* function, known
> // also on second pass.

The only difference is "nxt" versus "next". It is known after the first
pass that getfoo() returns struct foo *, and that nxt is to be a local
variable of that type.

Programming languages require explicit ways to define the existence variables,
local and global. This is true even if type declarations are not required.
For instance Lisp has "let" (and some other binding constructs): (let ((x 3))
...). This clearly establishes the programmer intent to bind a fresh variable
over the ... scope.

A variable which has no lexically apparent binding is a "free variable".
The assignment to or access of any free variable which isn't defined in the
pervasive global environment should be diagnosed, rather than interpreted
as something else.

Assignment should never be treated as a definition in any serious
language for software engineering. It's mildly acceptable in CS flunk
jobs like Awk and whatnot. In awk you can evaluate something like foo[42]++,
and the array comes to life with foo[42] ending up with 1.

Inference should be confined to the determination of type, not of existence.

C++ implements a form of type inference, by giving a new meaning to the
deprecated auto keyword. Suppose foo() is declared, and returns int. Then:

{
auto x = foo();

defines x to be of type int. The word "automatic" implied by "auto" neatly
changes meaning in C++. It doesn't refer to "automatic storage" as in historic
C, but "automatic type". :)

This explicit "auto" is volumes better than just:

x = foo();

If C is ever to do inference, there is very little reason to do it
differently from the proven C++ design, simply from a compatibility POV.

Kaz Kylheku

unread,
Nov 12, 2015, 1:37:37 PM11/12/15
to
On 2015-11-12, Rick C. Hodgin <rick.c...@gmail.com> wrote:
>> Oh dyslexia, dyslexia, how fare thou art... thy light shines upon me
>
> Ugh! "Fair" even. :-) Wow! I'm in rare form today.

Why correct a typo in a comment about dyslexia?
Just let the accident look like irony.

"I meant to do that!" -- Pee Wee Herman

Rick C. Hodgin

unread,
Nov 12, 2015, 1:59:20 PM11/12/15
to
I don't see how "nxt = getfoo();" would declare a variable of type
struct foo*, because it's never declared as such. To my knowledge,
C does not allow that type of anonymous creation as by return type.

> Programming languages require explicit ways to define the existence variables,
> local and global. This is true even if type declarations are not required.
> For instance Lisp has "let" (and some other binding constructs): (let ((x 3))
> ...). This clearly establishes the programmer intent to bind a fresh variable
> over the ... scope.
>
> A variable which has no lexically apparent binding is a "free variable".
> The assignment to or access of any free variable which isn't defined in the
> pervasive global environment should be diagnosed, rather than interpreted
> as something else.
>
> Assignment should never be treated as a definition in any serious
> language for software engineering. It's mildly acceptable in CS flunk
> jobs like Awk and whatnot. In awk you can evaluate something like foo[42]++,
> and the array comes to life with foo[42] ending up with 1.
>
> Inference should be confined to the determination of type, not of existence.

Where does nxt get defined in the example Wojtek gave? To my knowledge,
because it was never typed, it would be an error.

> C++ implements a form of type inference, by giving a new meaning to the
> deprecated auto keyword. Suppose foo() is declared, and returns int. Then:
>
> {
> auto x = foo();
>
> defines x to be of type int. The word "automatic" implied by "auto" neatly
> changes meaning in C++. It doesn't refer to "automatic storage" as in historic
> C, but "automatic type". :)
>
> This explicit "auto" is volumes better than just:
>
> x = foo();
>
> If C is ever to do inference, there is very little reason to do it
> differently from the proven C++ design, simply from a compatibility POV.

I think auto is one of the worst inventions ever in computer programming.
But I can see why it was needed in C++.

Rick C. Hodgin

unread,
Nov 12, 2015, 2:03:11 PM11/12/15
to
On Thursday, November 12, 2015 at 1:37:37 PM UTC-5, Kaz Kylheku wrote:
> On 2015-11-12, Rick C. Hodgin <rick.c...@gmail.com> wrote:
> >> Oh dyslexia, dyslexia, how fare thou art... thy light shines upon me
> >
> > Ugh! "Fair" even. :-) Wow! I'm in rare form today.
>
> Why correct a typo in a comment about dyslexia?
> Just let the accident look like irony.

It's really quite something. I think today's been my worst day ever.
It comes and goes to varying degrees, but is always there to some
degree. Today's been at least as bad as I've ever seen. I keep
missing word after word, or including the wrong word which, when I
read it back, was the wrong word because in my mind I was using the
right word. I even went to type "gDbfs[handle]" earlier today, and
for whatever reason could not get myself to type "gDbfs" properly.
I tried three times and finally typed "g[ctrl+spacebar]" and it
gave me a selection of variables with that name, and then I chose
one from the list.

Really something how it happens like that.

> "I meant to do that!" -- Pee Wee Herman

Honesty ... it makes it easier to remember how things actually
transpired. :-) Plus, a small degree of "shock and horror" at
my actual conveyance of such a thing.

Keith Thompson

unread,
Nov 12, 2015, 2:21:32 PM11/12/15
to
Kaz Kylheku <k...@kylheku.com> writes:
> On 2015-11-12, Rick C. Hodgin <rick.c...@gmail.com> wrote:
>> On Thursday, November 12, 2015 at 9:36:20 AM UTC-5, Kaz Kylheku wrote:
[...]
>>> Thus if you misspell the name of a variable in an assignment, you automatically
>>> get a declaration for a new variable, where the assignment has become an
>>> initialization:
>>>
>>> struct foo *next = 0;
>>> // ...
>>> // ...
>>> nxt = getfoo(); // Oops! Turns into "struct foo *nxt = getfoo();"
>>
>> I don't understand what you mean here. I see this as an error the
>> compiler would generate regarding not nxt being unknown. How would
>> this statement define "nxt = getfoo();" to be
>> "struct foo* nxt = getfoo();" ?
>
> Exactly the same way as in your proposal, quoted above, which I repeat:
>
>> int main( void ) {
>> next = getfoo(); // <==== var is local variable, known second pass
>> // getfoo() is struct foo* function, known
>> // also on second pass.
>
> The only difference is "nxt" versus "next". It is known after the first
> pass that getfoo() returns struct foo *, and that nxt is to be a local
> variable of that type.

I don't believe that's what's being proposed.

If I understand the proposal correctly, a call

nxt = getfoo();

with no visible declaration of `nxt` would be valid *if and
only if* there is a valid declaration of `nxt` later in the same
translation unit. There would be no implicit declarations (as there
were for functions in C90 and, I think, objects in pre-ANSI C).
Declarations are still mandatory; the change is that they may
appear after a reference to the declared entity. The type of `nxt`
would be determined by that declaration, not by the context in which
it's used.

If

nxt = getfoo()

is not preceded or followed by a declaration of nxt, it would
be invalid. If it's preceded or followed by a declaration of nxt
with a type not implicitly convertible from the type returned by
getfoo(), it would also be invalid.

This is not to imply that I support the proposed change. In my
opinion it would be too much effort for too little benefit.

<OT>
One or both of you might wonder why I'm replying to this. My killfile
(actually a scorefile) is specific to each newsgroup. Even if that were
not the case, there is no implied commitment on my part. If you feel
this statement is unnecessary, feel free to ignore it.
</OT>

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Richard Kettlewell

unread,
Nov 12, 2015, 2:31:32 PM11/12/15
to
int x;
void f(void) {
g(x); // expression or a declaration? parser can’t tell yet
}
typedef int g; // now we know it was a declaration
//void g(int); // alternatively it could have been an expression

(I like the proposal though.)

--
http://www.greenend.org.uk/rjk/

Rick C. Hodgin

unread,
Nov 12, 2015, 2:34:49 PM11/12/15
to
Correct.

> This is not to imply that I support the proposed change. In my
> opinion it would be too much effort for too little benefit.
>
> <OT>
> One or both of you might wonder why I'm replying to this. My killfile
> (actually a scorefile) is specific to each newsgroup. Even if that were
> not the case, there is no implied commitment on my part. If you feel
> this statement is unnecessary, feel free to ignore it.
> </OT>

Keith, I don't think you were able to see my post on the other
newsgroup, so please know this, as I repeat the statement here:
I am consistently impressed with your knowledge of C, and I am
thankful for your participation in the C groups.

Rick C. Hodgin

unread,
Nov 12, 2015, 2:35:11 PM11/12/15
to
Correct. It would require two passes.

Nick Bowler

unread,
Nov 12, 2015, 4:16:46 PM11/12/15
to
I think this proposal makes a lot of sense for declarations of
identifiers with internal or external linkage at file scope. For such
identifiers, we could treat file-scope similarly to how function-scope
works today (they will be visible everywhere in the translation unit
unless shadowed). This confers many advantages in my opinion. For
example, it becomes possible to write mutually recursive functions
without any redundant declarations.

For block scope variables, the advantages are less obvious and the
semantics become complicated. Here are some questions off the top of my
head that need consideration:

void f(void)
{
/* Naming a VLA prior to its declaration, currently a syntax error */
int n, *p;

p = a; /* What happens under this proposal? */
n = 1;

int a[n];

...
}

void g(void)
{
/* Self-referencing declaration, currently a syntax error */
int n[*n]; /* What happens under this proposal? */

...
}

void h(void)
{
/* Cyclic initializers: currently a syntax error */
int f = g;
int g = f; /* What happens under this proposal? */

...
}

void j(void)
{
/* More complex shadowing: currently valid. */
int n = 1;

{
int a = n; /* What happens under this proposal? */
int n;

...
}
}

While I could see it being convenient for typedefs as well there are
syntax implications which make it tricky. So restricting it to just
identifiers with linkage at file scope sidesteps most of the problems.

Nick Bowler

unread,
Nov 12, 2015, 4:36:14 PM11/12/15
to
On Wed, 11 Nov 2015 16:12:32 +0000, Kaz Kylheku wrote:
> On 2015-11-11, martin...@yahoo.co.uk <martin...@yahoo.co.uk> wrote:
>> If I understand the OP correctly: No, that would not be legal.
>>
>> What would be legal, is call somefn at one point in the compilation
>> unit, and then at some later point *in the same compilation unit*
>> define somefn.
>>
>> That would allow people to write the external functions first,
>> calling the local static functions which are defined lower down the
>> file.
>
> Anything which encourages this awful style of arranging a program, sets
> me dead against itself as a matter of a knee-jerk reflex. :)

It is often quite natural to read and write programs in this way
(broad strokes first, followed by details), except in C where it is
very awkward since it requires rendundant declarations. Many other
languages do not have this limitation.

You often see a similar style in mathematical papers, along the lines of:

let x represent f(y) + g(y), where
f is ...
g is ...
y is ...

Wojtek Lerch

unread,
Nov 12, 2015, 4:43:19 PM11/12/15
to
On 12-Nov-15 4:14 PM, Nick Bowler wrote:
> /* Cyclic initializers: currently a syntax error */
> int f = g;
> int g = f; /* What happens under this proposal? */

This is not really cyclic; you're just reading g before it's initialized.

If you want cyclic, how about this:

char foo[ 1 + sizeof bar ], bar[ 1 + sizeof foo ];

Nick Bowler

unread,
Nov 12, 2015, 5:06:01 PM11/12/15
to
On Thu, 12 Nov 2015 16:43:17 -0500, Wojtek Lerch wrote:
> On 12-Nov-15 4:14 PM, Nick Bowler wrote:
>> /* Cyclic initializers: currently a syntax error */
>> int f = g;
>> int g = f; /* What happens under this proposal? */
>
> This is not really cyclic; you're just reading g before it's initialized.

Right, the above case is already covered because the value of
g is indeterminate until the declaration of g is reached.
(n1570§6.2.4p6)

> If you want cyclic, how about this:
>
> char foo[ 1 + sizeof bar ], bar[ 1 + sizeof foo ];

Let's go with that. This case needs to be dealt with even if we
restrict the proposal to file scope only.

Kaz Kylheku

unread,
Nov 12, 2015, 5:25:12 PM11/12/15
to
On 2015-11-12, Nick Bowler <nbo...@draconx.ca> wrote:
> On Wed, 11 Nov 2015 16:12:32 +0000, Kaz Kylheku wrote:
>> On 2015-11-11, martin...@yahoo.co.uk <martin...@yahoo.co.uk> wrote:
>>> If I understand the OP correctly: No, that would not be legal.
>>>
>>> What would be legal, is call somefn at one point in the compilation
>>> unit, and then at some later point *in the same compilation unit*
>>> define somefn.
>>>
>>> That would allow people to write the external functions first,
>>> calling the local static functions which are defined lower down the
>>> file.
>>
>> Anything which encourages this awful style of arranging a program, sets
>> me dead against itself as a matter of a knee-jerk reflex. :)
>
> It is often quite natural to read and write programs in this way
> (broad strokes first, followed by details), except in C where it is
> very awkward since it requires rendundant declarations. Many other
> languages do not have this limitation.

You can put the broad strokes at the bottom, followed by details toward
the top.

I follow the low level first, higher levels later even in languages
where it is not required.

This is because

1) There *should* be an order among the functions, rather than chaos.
2) This order should proceed from their call dependencies
(i.e. it should be one of the possible topological sorts of their
dependency graph.)

So this leaves only two possible (kinds of) orders: bottom to top, or top to
bottom. It rules out scatterbrained orders where a given function definition is
embedded in the middle of its uses (except in cases of recursion).

The low to high order is better for a thorough understanding of a module
by a human reading the file in the natural order. The lower levels constitute a
language/vocabulary in terms of which the higher levels are expressed.

In this order, you do not have to leave holes in your understanding
which are filled later. You only have to go back if you forgot something.

Without the vocabulary of the lower level material, the high level
routines might not make any sense at all. "Oh, we allocate a two instances
of a splat based on the arguments, and then apply the blorch function
to them. Great, WTF does that mean?" I have to stash a meaningless piece
of syntax into my brain, in which "splat" and "blorch" are just meaningless
meta-syntactic identifiers to be filled later.

This kind of suspense is great in mystery novels, not when you're
reading code.

Here is the crux of it: bottom-to-top lets your brain the forms being defined
as immutable. Each form you see is complete and stays that way; the others
build on what was seen before without altering it.

Top-to-bottom requires ugly mutation: you form some incomplete representation
of a form in your mind, and then you have to go back and update it.

Immutability beats mutation, at least whenever both are equally viable.

Extending your understanding in a purely additive way, without having to refute
or revise prior knowledge, is ideal: constructing new objects in your "mind's
heap" which reference existing ones without alteration.

Thus when I'm writing Lisp code, or functions in a shell ~/.profile file or
what have you, I still put the lower-level function ones first. Same thing in
a C module in which I could use any order, because almost all the functions are
declared in a header.

The only problem is that the order sometimes decays when it is not enforced.

Lisp compilers suppress their "undefined function" warning diagnostic until
they process a translation unit (which can be a single source file, or
multiple, thanks to the with-translation-unit macro). It's actually good
to have such a warning which is immediately issues, and not deferred
until the end, so you can detect that your source is ill-ordered
for maintainability and readability.

Kaz Kylheku

unread,
Nov 12, 2015, 5:40:36 PM11/12/15
to
On 2015-11-12, Nick Bowler <nbo...@draconx.ca> wrote:
All identifiers here are getting bindings under the control and scope of the
let ... where ... construct. It doesn't have an arbitrary order; it is
understood that the "where" part is extraposed and considered first.
The bindings which it implies have a scope which extends over the binding
of x.

I can make myself a Lisp macro for this, something like:

(let-where ((var x (+ (f y) (g y))) ;; first binding, but established last
(fun f (arg) ...) ;; rest, established first (in parallel)
(fun g (arg) ...)
(var y ...))
.. body ...)

So the atual scope is that we have (f g y) in an outer scope which is
visible to an inner scope in which x is defined, so its initialization
form can access these. Then the body is evaluated in a scope where
x is visible.

I could have it so that all the var-s (except the first, of course) are scoped
outside of all the fun-s, regardless of their order, so above the bodies of f
and g can refer to the variable y, and use its value (which is established by
the time they are called). But the initialization form which gives a value to
y cannot use f or g.

No matter how I have it, there is a well-defined, documented order. If
the order is violated, then you get undefined references, or references
to the wrong entities in some outer scope which are not being shadowed
as expected.

Jakob Bohm

unread,
Nov 12, 2015, 5:44:58 PM11/12/15
to
The point is that the C language unambiguously encourages a specific
order of placement within the source code (declare before use, highest
level function at end of file) as opposed to more unstructured
languages that encourage different people to use opposite orders.

Note that this in no way prevents top-down design. When doing top-down
design/implementation in C, you keep editing the top of the file as you
fill in previously missing details used by stuff already in the file.
This is a great advantage if you have to deal with an editor that
doesn't remember the cursor position between invocations, as such
editors typically start with the cursor at top of file.

An even more sophisticated approach is data-driven design where you
start by declaring (a rough version of) the main data structures that
your code will use, then write functions (methods) to use them. Here C
will put the structures (whether designed first or last) at the top of
the file, where human (and computer) readers will see them before the
code that requires prior knowledge of those structures.

All in all, for anyone experienced in its use, the declare-before-use
rule is most certainly a good feature of the C language, and should not
be discarded because some beginners don't like it.

C is a professional tool for skilled professionals, it has all the
mechanical bits showing on the outside so they can be easily
manipulated by skilled users, it is not designed to be safely used by
beginners who don't know not to grab the sharp end or let loose hair
get entangled in the rotating parts.

C++ is an over-designed language increasingly feeling like a modern
version of PL/I. So many added features it is hard to keep track of by
humans and computers alike. The problems caused by declare-after-use
in C++ (such as the two styles of template resolution that competed for
a long time) are well known to long time users of that language, as are
the misguided "user protection" features of C++ constructors that
forced the invention of "2 phase construction" as a workaround.

BASIC in contrast was designed from the start for ease of use by
beginners and as such has very different rules than C.

(Note: I have used all of the languages mentioned, even at the same
time, each language has its own benefits and disadvantages and trying
to copy features between languages rarely improves the recipient
language).


Enjoy

Jakob
--
Jakob Bohm, CIO, Partner, WiseMo A/S. https://www.wisemo.com
Transformervej 29, 2860 Søborg, Denmark. Direct +45 31 13 16 10
This public discussion message is non-binding and may contain errors.
WiseMo - Remote Service Management for PCs, Phones and Embedded

Kaz Kylheku

unread,
Nov 12, 2015, 6:06:10 PM11/12/15
to
On 2015-11-12, Nick Bowler <nbo...@draconx.ca> wrote:
> You often see a similar style in mathematical papers, along the lines of:
>
> let x represent f(y) + g(y), where
> f is ...
> g is ...
> y is ...

On this topic, I should mention that in my little brain-child language TXR,
or rather its TXR Lisp dialect, I have a macro called mlet.

The mlet macro resembles let, or, rather more closely, let*. But it is
devious: the binding scan be written in any order. The macro works out
the correct order based on the dependencies among the variables
and initializing expressions.

If a circular dependency occurs, an error occurs (at evaluation time,
not expansion time).

See what you make of this:

http://www.nongnu.org/txr/txr-manpage.html#N-0169F4E1

What is the point of mlet; why not just write an ordered let?

;; Dependent calculations in arbitrary order
(mlet ((x (+ y 3))
(z (+ x 1))
(y 4))
(+ z 4)) --> 12


;; Error: circular reference:
;; x depends on y, y on z, but z on x again.
(mlet ((x (+ y 1))
(y (+ z 1))
(z (+ x 1)))
z)

There is a reason for its existence, explained in the example involving
lcons.

There can be a circular reference in mlet, if it is lazily evaluated:

(mlet ((list (lcons 1 list)))
list) --> (1 1 1 1 1 ...) ;; circular list

The (lcons 1 list) expression is evaluated in a scope in which list
is already bound. But that expression has lazy semantics: it returns
a lazy cons cell, which will not evaluate the "list" subexpression
until it is accessed. When the lazy cons cell is accessed, it evaluates
"list" and of course that evalutes to the cons cell itself. lcons
takes that value and places it into the cdr field of the lazy cons cell, thus
creating a circular list.

This will not work with regular let, because of the scoping: in
(let ((list ((lcons 1 list)))), the list expression refers to some outer
list binding, not to the one being set up.

This is markedly different from C. In C, the initializer expression
for an identifier does have that identifier in scope. This allows
things like:

struct node circ = { &circ };

So in a way, mlet is like C-style binding scoping for Lisp. Or at least
supports that sort of use case, but with the possibility of devious twists!

Kaz Kylheku

unread,
Nov 12, 2015, 6:15:13 PM11/12/15
to
On 2015-11-12, Wojtek Lerch <wojt...@yahoo.ca> wrote:
> On 12-Nov-15 4:14 PM, Nick Bowler wrote:
>> /* Cyclic initializers: currently a syntax error */
>> int f = g;
>> int g = f; /* What happens under this proposal? */
>
> This is not really cyclic; you're just reading g before it's initialized.

It is cyclic if it is understood that these declarations are considered
in parallel, and that the scope of both variables extends over both
of the initializing expressions.

(And that is pretty much what has to be understood if we take these proposals
seriously.)

This is exactly like my devious mlet invention in TXR Lisp that I just posted
about:

$ txr
This is the TXR Lisp interactive listener of TXR 123.
Use the :quit command or type Ctrl-D on empty line to exit.
1> (mlet ((f g)
(g f))
(+ f g))
** (expr-1:1) force: recursion forcing delayed form g (source location n/a)

On the other hand:

2> (mlet ((c a)
(b 3)
(a b))
(list a b c))
(3 3 3)

It's all in how you define the scoping and evaluation rules.

The difference is that I'm not crazy enough to make the whole language
work like this; it's confined inside a tidy binding construct. :)

Rick C. Hodgin

unread,
Nov 12, 2015, 6:35:48 PM11/12/15
to
The compiler could do one of two things. I think the first would be
the only appropriate resolution: (1) syntax error. (2) deferred
allocation until the source code line "int a[n];" is encountered, which
would cause "p = a;" to result in a syntax error because, on that line
in the source file, at the end of pass-2, there no "a" was in scope.

> ...
> }
>
> void g(void)
> {
> /* Self-referencing declaration, currently a syntax error */
> int n[*n]; /* What happens under this proposal? */

The compiler would identify in pass-1 that n is an array of unknown
size. It would then proceed to pass-2 to continue compilation,
where it would again come to this line and see n still as an array
of unknown size, and report the error.

> ...
> }
>
> void h(void)
> {
> /* Cyclic initializers: currently a syntax error */
> int f = g;
> int g = f; /* What happens under this proposal? */

No ambiguity here. In each case it would see f and g as ints, and
allocate them as such. In the function's prologue, it would assign
uninitialized g to f on the first line, then initialized-but-invalid
f to g on the second line.

> ...
> }
>
> void j(void)
> {
> /* More complex shadowing: currently valid. */
> int n = 1;
>
> {
> int a = n; /* What happens under this proposal? */
> int n;

I believe in C scoped variables have local scope within the { } block.
The same would be true here. The "int n;" line would be resolvable as
a scoped instance of n, and the "int a = n;" would initialize to that
instance's uninitialized value.

> ...
> }
> }
>
> While I could see it being convenient for typedefs as well there are
> syntax implications which make it tricky. So restricting it to just
> identifiers with linkage at file scope sidesteps most of the problems.

Those were excellent examples. :-)

Rick C. Hodgin

unread,
Nov 12, 2015, 6:36:51 PM11/12/15
to
In pass-1 it would see foo[] as a char array, but of unknown size, so it
would defer allocation until pass-2. The same for bar[]. On pass-2 it
would again retry to resolve foo[]'s size, and be unable, and report the
error. The same for bar[].

Wojtek Lerch

unread,
Nov 12, 2015, 9:27:54 PM11/12/15
to
On 12/11/2015 6:15 PM, Kaz Kylheku wrote:
> On 2015-11-12, Wojtek Lerch <wojt...@yahoo.ca> wrote:
>> On 12-Nov-15 4:14 PM, Nick Bowler wrote:
>>> /* Cyclic initializers: currently a syntax error */
>>> int f = g;
>>> int g = f; /* What happens under this proposal? */
>>
>> This is not really cyclic; you're just reading g before it's initialized.
>
> It is cyclic if it is understood that these declarations are considered
> in parallel,

I didn't think that was part of the proposal. Today's C has a sequence
point after each full declarator and there's no parallelism between
different initializers; I don't think the proposal mentioned changing that.

> and that the scope of both variables extends over both
> of the initializing expressions.

Even in the existing C, the lifetime of an automatic variable starts at
the beginning of the block; it's just the scope of its name that doesn't
start until the declaration. What we call an initializer is really
nothing more than an assignment, with a slightly different syntax, that
happens after the variable has already existed for a while and may even
have been accessed. Rick's proposal merely makes it much easier to
access variables before their declarations, because it allows you to use
the variable's name rather than having to mess with goto and pointers.

> (And that is pretty much what has to be understood if we take these proposals
> seriously.)

I don't see why, unless you assume that the proposal was inspired by
your LISP experiment...

Richard Damon

unread,
Nov 12, 2015, 10:38:53 PM11/12/15
to
How would you handle something like:


int a = sizeof b;

char b[ sizeof c+1];

char c [sizeof d+1];

char d [sizeof e+1];

char e[5];


Do we go to a pass-5 to figure out the value for a?

Kaz Kylheku

unread,
Nov 12, 2015, 11:18:04 PM11/12/15
to
On 2015-11-13, Wojtek Lerch <wojt...@yahoo.ca> wrote:
> On 12/11/2015 6:15 PM, Kaz Kylheku wrote:
>> On 2015-11-12, Wojtek Lerch <wojt...@yahoo.ca> wrote:
>>> On 12-Nov-15 4:14 PM, Nick Bowler wrote:
>>>> /* Cyclic initializers: currently a syntax error */
>>>> int f = g;
>>>> int g = f; /* What happens under this proposal? */
>>>
>>> This is not really cyclic; you're just reading g before it's initialized.
>>
>> It is cyclic if it is understood that these declarations are considered
>> in parallel,
>
> I didn't think that was part of the proposal. Today's C has a sequence
> point after each full declarator and there's no parallelism between
> different initializers; I don't think the proposal mentioned changing that.

Right; pardon me. What I mean by parallelism isn't parallel execution, but
parallel binding of names. This something that is taken care of at compile
time, as it were.

Thus if we have parallel binding in a fantasy construct like

parallel int f = x, g = y;

it means that the scope of f and g begins simultaneously. Moreover
the expressions x and y are both in the scope of f and g, or they else they are not.
(This depends on our choice of scoping rule.)

But x and y are definitely not evaluated in parallel. x is evaluated
first, then a sequence point occurs, then y is evaluated!

Parallel just means that f and g spring into existence at the same time.

With this we can do something like:

// two nodes that point to each other:

parallel struct node p = { &q }, q = { &p };

// Note: this assumes parallel binding plus the scoping rule
// that the initializers see the identifiers. This is compatible
// with existing C semantics that the initializer is immersed
// in the scope of identifier being declared, allowing for
// struct node selfref = { &selfref };

The other scoping rule lets us do things like this:

int x, y;

{
// swap x and y in inner scope:
parallel int x = y, y = x;

// Under this scoping rule, the initializers are not in
// the scope where the inner x and y are yet visible;
// they see the outer x and y.
}


>> (And that is pretty much what has to be understood if we take these proposals
>> seriously.)
>
> I don't see why, unless you assume that the proposal was inspired by
> your LISP experiment...

(Of course not, it's just that it has something to say about this sort of
thing. That mlet construct is a committed, documented feature in a programming
language that was put out some sixteen releases ago.)

Rick C. Hodgin

unread,
Nov 12, 2015, 11:54:32 PM11/12/15
to
On Thursday, November 12, 2015 at 10:38:53 PM UTC-5, Richard Damon wrote:
> On 11/12/15 6:36 PM, Rick C. Hodgin wrote:
> > On Thursday, November 12, 2015 at 4:43:19 PM UTC-5, Wojtek Lerch wrote:
> >> On 12-Nov-15 4:14 PM, Nick Bowler wrote:
> >>> /* Cyclic initializers: currently a syntax error */
> >>> int f = g;
> >>> int g = f; /* What happens under this proposal? */
> >>
> >> This is not really cyclic; you're just reading g before it's initialized.
> >>
> >> If you want cyclic, how about this:
> >>
> >> char foo[ 1 + sizeof bar ], bar[ 1 + sizeof foo ];
> >
> > In pass-1 it would see foo[] as a char array, but of unknown size, so it
> > would defer allocation until pass-2. The same for bar[]. On pass-2 it
> > would again retry to resolve foo[]'s size, and be unable, and report the
> > error. The same for bar[].
>
> How would you handle something like:
>
> int a = sizeof b;
> char b[ sizeof c+1];
> char c [sizeof d+1];
> char d [sizeof e+1];
> char e[5];

pass-1 would be able to declare e. pass-2 would be able to declare d,
but c, b, and a would all be undeclared, resulting in the compiler
reporting errors.

> Do we go to a pass-5 to figure out the value for a?

Everything that's unknown in pass-1 must be resolved at the end of
pass-2, otherwise it's an error and must be conducted as today with
forward declarations.

Richard Damon

unread,
Nov 13, 2015, 7:34:32 AM11/13/15
to
So you don't need forward declarations/ordering of definitions unless
you need them ordered. Sounds like a somewhat confusing definition of a
language. If you want to allow unordered definitions, they really should
be unordered (not just kind of), which does add a lot of complexity (or
requires some simplification of what you can say). The issue here with
starting from C, is that the C grammar really needs to know what type of
thing a symbol means in order to parse it.

For example:


foo(bar);


As a statement, if foo is a type, then this declares bar to be a
variable of type foo (with a redundant parenthesis. If foo is the name
of a function, or a variable of type pointer to function, this is a
statement which is a function call.

Rick C. Hodgin

unread,
Nov 13, 2015, 9:59:10 AM11/13/15
to
> So you don't need forward declarations/ordering of definitions unless
> you need them ordered. Sounds like a somewhat confusing definition of a
> language. If you want to allow unordered definitions, they really should
> be unordered (not just kind of), which does add a lot of complexity (or
> requires some simplification of what you can say). The issue here with
> starting from C, is that the C grammar really needs to know what type of
> thing a symbol means in order to parse it.

The proposal I have would allow for a two-pass compiler, instead of a
one-pass. It could easily be switched to an N-pass compiler, such that
so long as expressions are being parsed successfully on each pass then
continue, but when a pass is completed and nothing further could be
parsed, then generate an error.

It would only be fractionally marginally more difficult to implement
that N-pass solution than a two-pass solution. But, I don't think it's
necessary. Perhaps others do. And, it would be the best solution.

> For example:
> foo(bar);
>
> As a statement, if foo is a type, then this declares bar to be a
> variable of type foo (with a redundant parenthesis. If foo is the name
> of a function, or a variable of type pointer to function, this is a
> statement which is a function call.

If it's unknown as of the first pass, defer parsing until the second
pass. If it's still unknown then, report an error. And if the N-pass
solution is decided, defer parsing until nothing else can be parsed,
and then report the error.

BartC

unread,
Nov 13, 2015, 5:50:22 PM11/13/15
to
So how do you do this in C now with forward declarations?

Because if it can't be done, then there's no point in using it as an
argument against the proposal.

--
Bartc




BartC

unread,
Nov 13, 2015, 6:16:58 PM11/13/15
to
On 13/11/2015 12:34, Richard Damon wrote:
> On 11/12/15 11:54 PM, Rick C. Hodgin wrote:
>> On Thursday, November 12, 2015 at 10:38:53 PM UTC-5, Richard Damon wrote:

>>> int a = sizeof b;
>>> char b[ sizeof c+1];
>>> char c [sizeof d+1];
>>> char d [sizeof e+1];
>>> char e[5];
>>
>> pass-1 would be able to declare e. pass-2 would be able to declare d,
>> but c, b, and a would all be undeclared, resulting in the compiler
>> reporting errors.
>>
>>> Do we go to a pass-5 to figure out the value for a?
>>
>> Everything that's unknown in pass-1 must be resolved at the end of
>> pass-2, otherwise it's an error and must be conducted as today with
>> forward declarations.
>>
>> Best regards,
>> Rick C. Hodgin
>>
>
> So you don't need forward declarations/ordering of definitions unless
> you need them ordered. Sounds like a somewhat confusing definition of a
> language. If you want to allow unordered definitions, they really should
> be unordered (not just kind of), which does add a lot of complexity (or
> requires some simplification of what you can say). The issue here with
> starting from C, is that the C grammar really needs to know what type of
> thing a symbol means in order to parse it.

I've implemented a language which, like the proposal, generally doesn't
require forward declarations /except where necessary/.

That doesn't make the proposal worthless. It means, for example, that I
never need to write forward function declarations (well, except for
perhaps 1% of functions where there is a tricky case of mutual import
across modules).

That alone makes it incredibly useful. Because apart from anything else,
it's not just a forward declaration, but having to write the function
spec twice.

User-defined types need to be declared before use, because they can
affect how the following code is to be parsed. (And sometimes forward
type declarations are needed, again for mutual or cyclic references.)

Variables also can be declared anywhere within their scope (although
this is more because I didn't bother putting in the restriction and
didn't know I could do that until I found out by accident).

Again, the exception is where a particular order is necessary at
compile-time, such as your example:

> int a = sizeof b;
> char b[ sizeof c+1];
> char c [sizeof d+1];
> char d [sizeof e+1];
> char e[5];

Here I would need the order to be e, d, c, b, a. If this was that
important however, it could be done in a, b, c, d, e order with a bit
more work. (Type sizes are worked out in pass 2 instead of pass 1, and a
recursive method can be used so that sizes can be determined without
going to multiple passes just for this purpose.)

Labels for gotos, of course, don't need forward declarations. At least C
has managed to figure that one out.

--
Bartc


Wojtek Lerch

unread,
Nov 13, 2015, 10:39:37 PM11/13/15
to
How does that follow?

Anyway, I wasn't thinking about this example as a direct argument
against the (vague) proposal -- I see it as an example of where the
details of the specification could be tricky. That's where the devil
is, assuming that the goal is a proper language with a more or less
formal specification, as opposed to an implementation that defines a
language as "what the compiler accepts".

Wojtek Lerch

unread,
Nov 13, 2015, 10:45:13 PM11/13/15
to
On 13/11/2015 6:16 PM, BartC wrote:
> I've implemented a language which, like the proposal, generally doesn't
> require forward declarations /except where necessary/.

The same can be said about standard C. You left out an important bit:
where are they necessary? :)

> That doesn't make the proposal worthless. It means, for example, that I
> never need to write forward function declarations (well, except for
> perhaps 1% of functions where there is a tricky case of mutual import
> across modules).

That's funny, because I write in standard C and I'm pretty sure that the
percentage of functions in my code that need a forward declaration is
well below 1%. Or do you count external declarations in a header too?

> That alone makes it incredibly useful. Because apart from anything else,
> it's not just a forward declaration, but having to write the function
> spec twice.

In standard C, the only situations where you need to write the function
spec twice are when there's recursion involving more than one function
or when the function has external linkage. And personally I think that
having to write it twice in those cases is a good thing -- it makes your
laziness work in favour of better design, motivating you to write your
code with fewer external functions and with simpler interfaces between
translation units.

If you think that not having to write function specs twice is
"incredibly useful", what is your opinion about documentation? :)

BartC

unread,
Nov 14, 2015, 5:59:00 AM11/14/15
to
On 14/11/2015 03:45, Wojtek Lerch wrote:
> On 13/11/2015 6:16 PM, BartC wrote:
>> I've implemented a language which, like the proposal, generally doesn't
>> require forward declarations /except where necessary/.
>
> The same can be said about standard C. You left out an important bit:
> where are they necessary? :)
>
>> That doesn't make the proposal worthless. It means, for example, that I
>> never need to write forward function declarations (well, except for
>> perhaps 1% of functions where there is a tricky case of mutual import
>> across modules).
>
> That's funny, because I write in standard C and I'm pretty sure that the
> percentage of functions in my code that need a forward declaration is
> well below 1%. Or do you count external declarations in a header too?

Yes, I include all separate prototypes (ie forward declarations) that
exist. That includes all the ones that have to be maintained in header
files for those functions that are exported.

Looking only at local (static) functions, you can only get away without
forward declarations if the definitions are in a particular order. That
can mean, for example, writing main() at the end of the file.

That is a massive restriction on the freedom with which you write,
modify, rename, delete, or move functions within the file or between
files. With my scheme, I just never have to worry about it. Functions
can be in any order.

>> That alone makes it incredibly useful. Because apart from anything else,
>> it's not just a forward declaration, but having to write the function
>> spec twice.
>
> In standard C, the only situations where you need to write the function
> spec twice are when there's recursion involving more than one function
> or when the function has external linkage. And personally I think that
> having to write it twice in those cases is a good thing -- it makes your
> laziness work in favour of better design, motivating you to write your
> code with fewer external functions and with simpler interfaces between
> translation units.

> If you think that not having to write function specs twice is
> "incredibly useful", what is your opinion about documentation? :)

What about it? The scheme I use has a simple module system. If you have
module M, and want to share or export a function F, then you write
'global' in front of the definition. To call F from another module A,
you write 'import M' in that module. No headers or prototypes need to be
written and maintained by the programmer.

This works via the automatic generation of an exports file (which is
what is picked up with 'import'). Any document strings with the function
definition are also written to the exports file, which then also serves
as documentation.

The same scheme can share typedefs, structs, macros, variables and named
constants (the latter, C doesn't have). Name-spaces are thrown in for
good measure. Believe me, once you've used it you don't want to go back
to C!

(There are some problems but on the whole it's still more comfortable to
work with that C's prototypes and header files.)

However, C doesn't need to go that far. Just relaxing the rules on
function order within a module will be a useful first step.

--
Bartc

Kaz Kylheku

unread,
Nov 14, 2015, 11:53:40 AM11/14/15
to
On 2015-11-14, BartC <b...@freeuk.com> wrote:
> On 14/11/2015 03:45, Wojtek Lerch wrote:
>> On 13/11/2015 6:16 PM, BartC wrote:
>>> I've implemented a language which, like the proposal, generally doesn't
>>> require forward declarations /except where necessary/.
>>
>> The same can be said about standard C. You left out an important bit:
>> where are they necessary? :)
>>
>>> That doesn't make the proposal worthless. It means, for example, that I
>>> never need to write forward function declarations (well, except for
>>> perhaps 1% of functions where there is a tricky case of mutual import
>>> across modules).
>>
>> That's funny, because I write in standard C and I'm pretty sure that the
>> percentage of functions in my code that need a forward declaration is
>> well below 1%. Or do you count external declarations in a header too?
>
> Yes, I include all separate prototypes (ie forward declarations) that
> exist. That includes all the ones that have to be maintained in header
> files for those functions that are exported.

Even the Pascal-like languages have declarations in module definition files.

Declarations are necessary by the underlying object file technology,
which is sometimes dictated to the C compiler people.

COFF isn't going to cough up type info any time soon.

Someone who hates maintaining headers can automate it. A tool for
this exists: makeheaders: http://www.hwaci.com/sw/mkhdr/

> What about it? The scheme I use has a simple module system. If you have
> module M, and want to share or export a function F, then you write
> 'global' in front of the definition. To call F from another module A,
> you write 'import M' in that module. No headers or prototypes need to be
> written and maintained by the programmer.

This means that to compile a module B which is a client of A, you have to
process the source code of A. Or else you have a fancy object file format which
contains all the interface info about A.

The first option is a nonstarter. Even in the open source world, I don't
want all the sources to glibc installed just to compile a C program,
so that it can find the right file where printf is defined and pull out
the declaration.

> This works via the automatic generation of an exports file (which is
> what is picked up with 'import').

Aha: equivalent of "makeheaders" above.

BartC

unread,
Nov 14, 2015, 1:07:19 PM11/14/15
to
On 14/11/2015 16:53, Kaz Kylheku wrote:
> On 2015-11-14, BartC <b...@freeuk.com> wrote:

>> Yes, I include all separate prototypes (ie forward declarations) that
>> exist. That includes all the ones that have to be maintained in header
>> files for those functions that are exported.

> Someone who hates maintaining headers can automate it. A tool for
> this exists: makeheaders: http://www.hwaci.com/sw/mkhdr/
>
>> What about it? The scheme I use has a simple module system. If you have
>> module M, and want to share or export a function F, then you write
>> 'global' in front of the definition. To call F from another module A,
>> you write 'import M' in that module. No headers or prototypes need to be
>> written and maintained by the programmer.
>
> This means that to compile a module B which is a client of A, you have to
> process the source code of A. Or else you have a fancy object file format which
> contains all the interface info about A.
>
> The first option is a nonstarter. Even in the open source world, I don't
> want all the sources to glibc installed just to compile a C program,
> so that it can find the right file where printf is defined and pull out
> the declaration.

If B imports A, then you don't need the source code for A. It does need
to have been compiled before B, so that the compiler will have created
an exports file (the equivalent of a header). It is this exports file
that is read in by B. (These files are just text and can also be written
by hand.)

For distributing a library, the exports file does the same job as a
header might do.

I don't know how you might retro-fit a module system on to C. Probably
it will have to end up with conventional headers.

But the proposal in this thread I think was more about removing
unnecessary declarations within files rather than finding new ways of
sharing global functions and such, otherwise it would be a far more
ambitious and disruptive change to the language.

>> This works via the automatic generation of an exports file (which is
>> what is picked up with 'import').
>
> Aha: equivalent of "makeheaders" above.

Yes, I had a scheme working with C at one point. A very simple script
(helped by writing declarations in a certain style) that proprocessed C
code, and generated .cl files for local function prototypes (and that
was #included in the module itself), and .cx files for exported
functions that would be #included in a header.

So simple, that you'd think a compiler would be able to do something
similar with little trouble. It wouldn't need to physically generate the
file of local function prototypes either.

--
Bartc

Francis Glassborow

unread,
Nov 15, 2015, 6:35:11 AM11/15/15
to
I am finding it increasingly difficult to understand why people want to
fix something which is a non problem for most of us.

There is no need to fix the language when tools will do what we require.
'makeheaders' is an example of a tool that effectively solves the
problem of the ordering of function definitions within a source code
file. Just write the file in any way that you feel comfortable with.

In the days when I was writing much C code I used to work with a .c file
and a .h file open in my editor. The .c file #included the .h file and I
simply added declarations of functions that I needed to call into the .h
file as I worked top down through my coding of an application. When I
was ready I added the definition to the .c file. The linker would tell
me if I had still to define a function that had be declared in the ,h
file and used in the .c file.

This was hardly a difficult way to proceed and left me the option of
pulling low level definitions into their own file if I decided that I
was going to reuse them.

Francis

BartC

unread,
Nov 15, 2015, 7:19:27 AM11/15/15
to
On 15/11/2015 11:35, Francis Glassborow wrote:
> On 14/11/2015 18:07, BartC wrote:

>> But the proposal in this thread I think was more about removing
>> unnecessary declarations within files rather than finding new ways of
>> sharing global functions and such, otherwise it would be a far more
>> ambitious and disruptive change to the language.

> There is no need to fix the language when tools will do what we require.
> 'makeheaders' is an example of a tool that effectively solves the
> problem of the ordering of function definitions within a source code
> file. Just write the file in any way that you feel comfortable with.

> In the days when I was writing much C code I used to work with a .c file
> and a .h file open in my editor. The .c file #included the .h file and I
> simply added declarations of functions that I needed to call into the .h
> file as I worked top down through my coding of an application. When I
> was ready I added the definition to the .c file. The linker would tell
> me if I had still to define a function that had be declared in the ,h
> file and used in the .c file.
>
> This was hardly a difficult way to proceed and left me the option of
> pulling low level definitions into their own file if I decided that I
> was going to reuse them.

Your post is contradictory.

You say it's not a problem to have to maintain separate function
prototypes, then you say tools exist to do that.

Why would the authors of such a tool waste their time fixing a non-problem?

Why would the creators of newer languages such as Go do away with the
need for having to pre-declare functions ahead of use? According to this
thread, it gives many benefits and is no hassle at all!

--
Bartc

Kaz Kylheku

unread,
Nov 15, 2015, 11:33:06 AM11/15/15
to
On 2015-11-15, Francis Glassborow <francis.g...@btinternet.com> wrote:
> I am finding it increasingly difficult to understand why people want to
> fix something which is a non problem for most of us.

I don't understand why people want to fix trivial stupidities, while
remaining in the Blub computational model.

It's like living in a mud hut with no running water or electricity,
but obsessing that the floors and walls aren't level.

Kaz Kylheku

unread,
Nov 15, 2015, 11:39:37 AM11/15/15
to
Because it's easy to do, and resembles progress, so it feels good.

BartC

unread,
Nov 15, 2015, 12:28:16 PM11/15/15
to
C isn't exactly a mud hut. It's still an important language that
underpins a lot of software including other languages, or that is used
as a target for other, 'better' languages.

Given that, it is ludicrous that the following code:

int main(void) {
int a,b=0,c=0;
a=add(b,c);
}

float add(float a,float b) { return a+b;}

doesn't work because the compiler refuses to acknowledge the definition
of 'add' 3 lines further down.

It blithely assumes that add() takes int parameters and returns an int
result and is surprised when the actual definition disagrees with that
assumption. /That/ is the stone-age mentality we're trying to get away
from. No reason to brush it under the carpet as just another quirk
because we all have a choice of better languages to use instead.

--
Bartc

Francis Glassborow

unread,
Nov 15, 2015, 12:54:15 PM11/15/15
to
On 15/11/2015 17:28, BartC wrote:
> On 15/11/2015 16:33, Kaz Kylheku wrote:
>> On 2015-11-15, Francis Glassborow <francis.g...@btinternet.com>
>> wrote:
>>> I am finding it increasingly difficult to understand why people want to
>>> fix something which is a non problem for most of us.
>>
>> I don't understand why people want to fix trivial stupidities, while
>> remaining in the Blub computational model.
>>
>> It's like living in a mud hut with no running water or electricity,
>> but obsessing that the floors and walls aren't level.
>
> C isn't exactly a mud hut. It's still an important language that
> underpins a lot of software including other languages, or that is used
> as a target for other, 'better' languages.
>
> Given that, it is ludicrous that the following code:
>
> int main(void) {
> int a,b=0,c=0;
> a=add(b,c);
> }
>
> float add(float a,float b) { return a+b;}
>
> doesn't work because the compiler refuses to acknowledge the definition
> of 'add' 3 lines further down.
>
> It blithely assumes that add() takes int parameters and returns an int
no it does NOT. It issues a diagnostic. Implicit function declarations
went out quite a while ago.

> result and is surprised when the actual definition disagrees with that
> assumption. /That/ is the stone-age mentality we're trying to get away
> from. No reason to brush it under the carpet as just another quirk
> because we all have a choice of better languages to use instead.
>

Well then, go away and use them.

Francis

Rick C. Hodgin

unread,
Nov 15, 2015, 12:58:03 PM11/15/15
to
On Sunday, November 15, 2015 at 11:33:06 AM UTC-5, Kaz Kylheku wrote:
> On 2015-11-15, Francis Glassborow <francis.g...@btinternet.com> wrote:
> > I am finding it increasingly difficult to understand why people want to
> > fix something which is a non problem for most of us.
>
> I don't understand why people want to fix trivial stupidities, while
> remaining in the Blub computational model.

Over the course of my programming career, I bet I've lost a month's worth
of developer time dealing with setting up and maintaining header files in
C. In fact, I'd be surprised if that's anything less than a low estimate.

It's something we must do every day we write C code. And it's just not
necessary in almost all cases.

> It's like living in a mud hut with no running water or electricity,
> but obsessing that the floors and walls aren't level.

It's something that's no longer required. The mud huts that were all-but-
required in C code back in the 1970s... they've been replaced as new
materials have been found. These new materials give us more square footage,
better insulation, are tidy, clean, able to be expanded upon.

We no longer have to live in the mud. We can step up now. So ... why not?

I don't understand why people take your position, Kaz. Why would you want
to continue to live in any part of the mud when it's not a requirement?

Rick C. Hodgin

unread,
Nov 15, 2015, 1:05:30 PM11/15/15
to
It is progress, Kaz. It is clear progress. It is the way of all
languages in moving forward because maintaining header files is no
longer necessary. The computer can now do it for you in a few
milliseconds.

Our machines have matured, Kaz. More simultaneous CPU cores. More
memory. More hard disk space. More network bandwidth. Instant
communication with all parts of the Earth online. In fact, as I read
an article the other day, we're about 18 years ahead of the curve of
what was considered a "supercomputer" 18 years ago. We all have
computers beyond what supercomputers would've been back when C was
created. We all have these on our desktops, notebooks, even our
phones. We don't need to limit ourselves to the ways of the past.

If, however, you do want to limit yourself to the way things used to
be back when you were younger, then after this proposal is included
in the next C standard document, you can always choose to use an older
C compiler and then be happy.

BartC

unread,
Nov 15, 2015, 1:24:18 PM11/15/15
to
On 15/11/2015 17:54, Francis Glassborow wrote:
> On 15/11/2015 17:28, BartC wrote:
>> On 15/11/2015 16:33, Kaz Kylheku wrote:
>>> On 2015-11-15, Francis Glassborow <francis.g...@btinternet.com>
>>> wrote:
>>>> I am finding it increasingly difficult to understand why people want to
>>>> fix something which is a non problem for most of us.
>>>
>>> I don't understand why people want to fix trivial stupidities, while
>>> remaining in the Blub computational model.
>>>
>>> It's like living in a mud hut with no running water or electricity,
>>> but obsessing that the floors and walls aren't level.
>>
>> C isn't exactly a mud hut. It's still an important language that
>> underpins a lot of software including other languages, or that is used
>> as a target for other, 'better' languages.
>>
>> Given that, it is ludicrous that the following code:
>>
>> int main(void) {
>> int a,b=0,c=0;
>> a=add(b,c);
>> }
>>
>> float add(float a,float b) { return a+b;}
>>
>> doesn't work because the compiler refuses to acknowledge the definition
>> of 'add' 3 lines further down.
>>
>> It blithely assumes that add() takes int parameters and returns an int

> no it does NOT. It issues a diagnostic. Implicit function declarations
> went out quite a while ago.

I've just tried seven C compilers on that main() function. Four gave me
a warning about the implicit declaration, but didn't stop generating
code that assumed implicit ints. The other three (which included gcc on
Linux) said nothing. (All with default options.)

When I added in the float function, all seven report conflicting types
on that definition. Why? Because the compiler /assumed implicit ints/ on
the first use of add(). So they're still alive and kicking.

>> result and is surprised when the actual definition disagrees with that
>> assumption. /That/ is the stone-age mentality we're trying to get away
>> from. No reason to brush it under the carpet as just another quirk
>> because we all have a choice of better languages to use instead.
>>
> Well then, go away and use them.

I do. But I can't move without encountering C or its quirks on every
side. One of my compilers also generates C code. So for me the problems
are relevant.

--
Bartc

Keith Thompson

unread,
Nov 15, 2015, 2:07:50 PM11/15/15
to
BartC <b...@freeuk.com> writes:
[...]
>>> Given that, it is ludicrous that the following code:
>>>
>>> int main(void) {
>>> int a,b=0,c=0;
>>> a=add(b,c);
>>> }
>>>
>>> float add(float a,float b) { return a+b;}
>>>
>>> doesn't work because the compiler refuses to acknowledge the definition
>>> of 'add' 3 lines further down.
>>>
>>> It blithely assumes that add() takes int parameters and returns an int
>
>> no it does NOT. It issues a diagnostic. Implicit function declarations
>> went out quite a while ago.
>
> I've just tried seven C compilers on that main() function. Four gave me
> a warning about the implicit declaration, but didn't stop generating
> code that assumed implicit ints. The other three (which included gcc on
> Linux) said nothing. (All with default options.)
>
> When I added in the float function, all seven report conflicting types
> on that definition. Why? Because the compiler /assumed implicit ints/ on
> the first use of add(). So they're still alive and kicking.

Historically, gcc has defaulted to "-std=gnu89", which implements C90
with GNU-specific extensions. Starting with, if I'm not mistaken,
release 5.0, it defaults to "-std=gnu11", which will at least issue a
warning for a call to an undeclared function. After issuing the
warning (which is all that's required for a constraint violation), it
still assumes the C90-style implicit declaration.

gcc is not a conforming C compiler by default.

I personally consider it unfortunate that gcc has taken so long to
update its default behavior, and that it's so lax about some constraint
violations (issuing non-fatal warnings). But there's no need to use gcc
with its default settings. (Yes, using non-default settings is a bit
more work.)

I don't know as much about how other C compilers behave.

In any case, calling a function with no visible declaration has been a
constraint violation in standard C since 16 years ago -- and this is
comp.std.c.

[...]

> I do. But I can't move without encountering C or its quirks on every
> side. One of my compilers also generates C code. So for me the problems
> are relevant.

So figure out how to invoke a C compiler in conforming mode, and don't
ignore warnings (use -Werror or -pedantic-errors if you like).

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Wojtek Lerch

unread,
Nov 15, 2015, 6:25:15 PM11/15/15
to
On 14/11/2015 5:58 AM, BartC wrote:
> On 14/11/2015 03:45, Wojtek Lerch wrote:
>> On 13/11/2015 6:16 PM, BartC wrote:
>>> I've implemented a language which, like the proposal, generally doesn't
>>> require forward declarations /except where necessary/.
...
> Looking only at local (static) functions, you can only get away without
> forward declarations if the definitions are in a particular order. That
> can mean, for example, writing main() at the end of the file.
> That is a massive restriction on the freedom with which you write,
> modify, rename, delete, or move functions within the file or between
> files. With my scheme, I just never have to worry about it. Functions
> can be in any order.

True, but I don't see that as a good thing. A good programming language
needs restrictions that make it easier for humans to follow one
another's code. Knowing that most functions are defined before they're
first called makes them a little easier to find. Too much freedom
invites chaos. Programming requires discipline and order.

>>> That alone makes it incredibly useful. Because apart from anything else,
>>> it's not just a forward declaration, but having to write the function
>>> spec twice.
...
>> If you think that not having to write function specs twice is
>> "incredibly useful", what is your opinion about documentation? :)
>
> What about it?

Documentation usually contains a third copy of the function spec, along
with some prose that tends to take much more work to write than pasting
the function spec does. Do you find the ability not to write any
documentation "extremely useful" too?

Me, I often start writing drafts of documentation before I write the
first line of code. Even more often, I have a header containing the
function prototypes before I start implementing them. The opinion that
avoiding writing headers could be "incredibly useful" sounds about as
strange to me as the need to avoid documenting things. The amount of
work I could save if I didn't have to write headers by hand would be
completely negligible.

> The scheme I use has a simple module system. If you have
> module M, and want to share or export a function F, then you write
> 'global' in front of the definition. To call F from another module A,
> you write 'import M' in that module. No headers or prototypes need to be
> written and maintained by the programmer.

I guess it's largely a matter of opinion, but I prefer to have headers
and documentation in separate files, maintained by hand rather than
generated from the implementation code. This makes it more obvious when
a change causes a binary incompatibility, or a source incompatibility,
or a documentation incompatibility. (It would be nice to have an
automated way to verify that the prototypes in documentation match the
ones in the header. The closest thing available to me is using
documentation comments in headers -- there are a few things I don't like
about it but I've learned to live with it.)

Of course your priorities may be different. I often work on libraries
with customer-facing APIs; I feel that writing documentation first helps
me come up with interfaces that are simpler to understand and don't
unnecessarily expose implementation details. If your work involves
fewer libraries and more complete programs, it could be that the quality
of your internal interfaces and their documentation is less of a concern.

...
> However, C doesn't need to go that far. Just relaxing the rules on
> function order within a module will be a useful first step.

I don't mind agreeing to disagree about its usefulness. :)

Rick C. Hodgin

unread,
Nov 15, 2015, 7:23:49 PM11/15/15
to
On Sunday, November 15, 2015 at 6:25:15 PM UTC-5, Wojtek Lerch wrote:
> On 14/11/2015 5:58 AM, BartC wrote:
> > On 14/11/2015 03:45, Wojtek Lerch wrote:
> >> On 13/11/2015 6:16 PM, BartC wrote:
> >>> I've implemented a language which, like the proposal, generally doesn't
> >>> require forward declarations /except where necessary/.
> ...
> > Looking only at local (static) functions, you can only get away without
> > forward declarations if the definitions are in a particular order. That
> > can mean, for example, writing main() at the end of the file.
> > That is a massive restriction on the freedom with which you write,
> > modify, rename, delete, or move functions within the file or between
> > files. With my scheme, I just never have to worry about it. Functions
> > can be in any order.
>
> True, but I don't see that as a good thing. A good programming language
> needs restrictions that make it easier for humans to follow one
> another's code. Knowing that most functions are defined before they're
> first called makes them a little easier to find. Too much freedom
> invites chaos. Programming requires discipline and order.

We live in the age of developer-assisting editors and debuggers. We do
not live in the age of limited tools, limited processing power, limited
memory, and limited hard disk space. We live in the age where we have
a half dozen or more CPU cores at our disposal, and now we can run all
sorts of helper threads in the background to constantly search for
things relative to our cursor, to bring up help documents, bring up
nearby usages of the token name, and so on.

We live in an increasing age of power, and that power should be present
in our tools... after all, we are the compiler writers that other people
use to write their tools in! Our tools should have at least some of
those abilities we're enabling in others.

We can start here with removing the requirement of forward declarations,
and then move forward from there.

Rhialto

unread,
Nov 15, 2015, 7:24:21 PM11/15/15
to
In article <lnbnazq...@kst-u.example.com>,
Keith Thompson <ks...@mib.org> wrote:
>Declarations are still mandatory; the change is that they may
>appear after a reference to the declared entity.


>If
>
> nxt = getfoo()
>
>is not preceded or followed by a declaration of nxt, it would
>be invalid. If it's preceded or followed by a declaration of nxt
>with a type not implicitly convertible from the type returned by
>getfoo(), it would also be invalid.

There is a precedent in langugages like Algol-68.

In Algol-68, any declared name can be used in the whole (innermost)
block of its declaration. That holds for variables, functions,
structures, operators, etc etc, and even operator priorities. Especially
the last one leads to parsing nightmares, since 1 + 2 * 3 can suddenly
change meaning if inside the same or an enclosing block, a PRIO
declaration occurs which changes the relative priorities of + and *.

But it is very convenient for the programmer.

-Olaf.
--
___ Olaf 'Rhialto' Seibert -- The Doctor: No, 'eureka' is Greek for
\X/ rhialto/at/xs4all.nl -- 'this bath is too hot.'

Rhialto

unread,
Nov 15, 2015, 7:48:00 PM11/15/15
to
In article <201511122...@kylheku.com>,
Kaz Kylheku <k...@kylheku.com> wrote:
>Thus if we have parallel binding in a fantasy construct like
>
> parallel int f = x, g = y;
>
>it means that the scope of f and g begins simultaneously. Moreover
>the expressions x and y are both in the scope of f and g, or they else
>they are not.
>(This depends on our choice of scoping rule.)
>
>But x and y are definitely not evaluated in parallel. x is evaluated
>first, then a sequence point occurs, then y is evaluated!
>
>Parallel just means that f and g spring into existence at the same time.

This, also, has a precedent in Algol-68.

If you write INT a = 1, b = 2; you have a "collateral" declaration, but
if you write INT a = 1; INT b = 2; you have two sequential ones.

In the first case the order of execution is undefined and you cannot say
INT a = 1, b = a;

Francis Glassborow

unread,
Nov 16, 2015, 5:49:27 AM11/16/15
to
On 16/11/2015 00:23, Rick C. Hodgin wrote:
>> ....
> We live in the age of developer-assisting editors and debuggers. We do
> not live in the age of limited tools, limited processing power, limited
> memory, and limited hard disk space. We live in the age where we have
> a half dozen or more CPU cores at our disposal, and now we can run all
> sorts of helper threads in the background to constantly search for
> things relative to our cursor, to bring up help documents, bring up
> nearby usages of the token name, and so on.
>
> We live in an increasing age of power, and that power should be present
> in our tools... after all, we are the compiler writers that other people
> use to write their tools in! Our tools should have at least some of
> those abilities we're enabling in others.
>
> We can start here with removing the requirement of forward declarations,
> and then move forward from there.
>

For me, one of my most important tools is a compiler. Exactly because
modern hardware is so much faster I can use it frequently to check my
progress. I like any function I write to compile clean before I move on.
I also like to work top down. Nothing in a standard C compiler stops me
doing that. As I write my top level function I just add to my 'todo'
list as I call functions. It just happens that that list is called a
header file. When I have finished the top level function I move on to
the top of my 'todo' list. and repeat the process. At some stage I will
think I have finished and can try linking my code.

One very difficult thin to teach those intending to be programmers is to
be disciplined.

The only relaxation that I would even consider would be for static
functions. That might actually encourage more programmers to declare
their functions as static until they know they want to reuse them
elsewhere. But I am also of the school of thought that thinks variables
should be declared const until you find that you need to make them
mutable. And that style leads almost inevitably to a healthy
'declare/define' one variable per declarative statement.

Instead of trying to fix the language try fixing education. Of course
that is difficult but the rewards are high.

If you are using generated C code I suggest that you should not even be
looking at it. The generator should be producing linkable code Looking
at and trying to understand generated C is exactly like looking at and
trying to understand object code produced by a C compiler only really
useful in identifying a bug in the compiler.

Francis

BartC

unread,
Nov 16, 2015, 7:09:56 AM11/16/15
to
On 16/11/2015 10:49, Francis Glassborow wrote:

> The only relaxation that I would even consider would be for static
> functions. That might actually encourage more programmers to declare
> their functions as static until they know they want to reuse them
> elsewhere. But I am also of the school of thought that thinks variables
> should be declared const until you find that you need to make them
> mutable. And that style leads almost inevitably to a healthy
> 'declare/define' one variable per declarative statement.
>
> Instead of trying to fix the language try fixing education. Of course
> that is difficult but the rewards are high.

That's a good point. You can make a language easier to teach by having
fewer rules and quirks. But C has quirks by the bucket load! The
following is legal C for example:

int (*fooptr)();
int (*fooptr)();
int (*fooptr)();

int main(void){

fooptr("one","two","three");
fooptr(192.7,89.7);
fooptr(&fooptr);
(*******************fooptr)(&fooptr);
}

(Yes, you can redeclare the same thing over and over again; you can call
a function declared with no parameters, with anything at all; and you
can dereference a single level of function pointer as many times as you
like.)

Do we really have to teach all this stuff to a million students, or do
we try and fix the language? (And do we also teach them how to use Cdecl?!)

> If you are using generated C code I suggest that you should not even be
> looking at it. The generator should be producing linkable code Looking
> at and trying to understand generated C is exactly like looking at and
> trying to understand object code produced by a C compiler only really
> useful in identifying a bug in the compiler.

I like my generated code to look good. Partly because sometimes I have
to debug it, also because if I want to share programs, I have to
distribute C source code, and other people would be looking at it.

In fact, the layout of my generated code is better and more consistent
that much open source software I've seen (although expressions are very
'busy' at the minute with too many casts and parentheses). (Example:
http://pastebin.com/qUymXkzy)

In the case of function declarations, because of the C rules, *all*
functions in a module have their own separate prototype declaration at
the top of file. Imported functions are declared too (my generated code
doesn't use headers except for standard ones).

--
Bartc

Richard Bos

unread,
Nov 16, 2015, 8:53:56 AM11/16/15
to
Francis Glassborow <francis.g...@btinternet.com> wrote:

> On 14/11/2015 18:07, BartC wrote:

> > So simple, that you'd think a compiler would be able to do something
> > similar with little trouble. It wouldn't need to physically generate the
> > file of local function prototypes either.
> >
> I am finding it increasingly difficult to understand why people want to
> fix something which is a non problem for most of us.

It's "My Pet Language" Syndrome - or in this case, "My Pet Improvement
On The C Language" Syndrome. There's no technical reason; the purpose is
to create fuzzy feelings of intellectual superiority.

Richard

BartC

unread,
Nov 16, 2015, 9:41:35 AM11/16/15
to
On 16/11/2015 13:53, Richard Bos wrote:
> Francis Glassborow <francis.g...@btinternet.com> wrote:
>
>> On 14/11/2015 18:07, BartC wrote:
>
>>> So simple, that you'd think a compiler would be able to do something
>>> similar with little trouble. It wouldn't need to physically generate the
>>> file of local function prototypes either.
>>>
>> I am finding it increasingly difficult to understand why people want to
>> fix something which is a non problem for most of us.
>
> It's "My Pet Language" Syndrome - or in this case, "My Pet Improvement
> On The C Language" Syndrome.

I'd used my own 'pet' language for at least ten years before I first
tried using C, and for some 30 years before I wrote my first sizeable
project in C. That language was used in my work and for some commercial
applications.

It has many dozens of nice features that C could benefit from (including
left-to-right type specifications and, more recently, dropping the need
for forward function declarations that we're concerned with here).

Going the other way, there are only a handful of features I've adopted
from C (such as 0x123 for hex, and pointer offsets as counts rather than
bytes).

> There's no technical reason;

Huh? The technical reason in my case was that I needed a language for my
work. And one that could be instantly compiled and run, rather than
spending minutes of floppy disk-grinding to build the simplest program
(this was for 8-bit equipment). Commercial compilers also cost money (in
1982) and actually getting a version on compatible disks wasn't that
easy either.

(And anyway, the first compiler I put together was for a home-made
machine without a floppy disk drive. I remember programming 3D vector
graphics and image capture with it. )

> the purpose is
> to create fuzzy feelings of intellectual superiority.

Yes, there's some truth in that, although I was more bemused than
anything else. The intellectual superiority I suppose balances the
inferiority I feel when I look at Lisp or Haskell!

--
Bartc

Keith Thompson

unread,
Nov 16, 2015, 1:10:39 PM11/16/15
to
BartC <b...@freeuk.com> writes:
> On 16/11/2015 10:49, Francis Glassborow wrote:
[...]
>> Instead of trying to fix the language try fixing education. Of course
>> that is difficult but the rewards are high.
>
> That's a good point. You can make a language easier to teach by having
> fewer rules and quirks. But C has quirks by the bucket load! The
> following is legal C for example:
>
> int (*fooptr)();
> int (*fooptr)();
> int (*fooptr)();
>
> int main(void){
>
> fooptr("one","two","three");
> fooptr(192.7,89.7);
> fooptr(&fooptr);
> (*******************fooptr)(&fooptr);
> }
>
> (Yes, you can redeclare the same thing over and over again; you can call
> a function declared with no parameters, with anything at all; and you
> can dereference a single level of function pointer as many times as you
> like.)

As far as the function declaration with empty parentheses is concerned,
that's an old-style declaration. It's been an obsolescent feature since
1989. (I'm personally disappointed that it hasn't been removed from the
language, but I can live with it.) All we really need to teach is
"Don't do that." If you want a function with no parameters, use
`(void)`.

Why is being able to declare the same function more than once a problem?
The compiler checks that the declarations are consistent, and you don't
*have* to repeat yourself. Nor do you have to write (******fooptr)(),
any more than you have to write + + + + + + 42.

[...]

BartC

unread,
Nov 16, 2015, 2:02:06 PM11/16/15
to
On 16/11/2015 18:10, Keith Thompson wrote:
> BartC <b...@freeuk.com> writes:
>> On 16/11/2015 10:49, Francis Glassborow wrote:
> [...]
>>> Instead of trying to fix the language try fixing education. Of course
>>> that is difficult but the rewards are high.
>>
>> That's a good point. You can make a language easier to teach by having
>> fewer rules and quirks. But C has quirks by the bucket load! The
>> following is legal C for example:
>>
>> int (*fooptr)();
>> int (*fooptr)();
>> int (*fooptr)();
>>
>> int main(void){
>>
>> fooptr("one","two","three");
>> fooptr(192.7,89.7);
>> fooptr(&fooptr);
>> (*******************fooptr)(&fooptr);
>> }
>>
>> (Yes, you can redeclare the same thing over and over again; you can call
>> a function declared with no parameters, with anything at all; and you
>> can dereference a single level of function pointer as many times as you
>> like.)
>
> As far as the function declaration with empty parentheses is concerned,
> that's an old-style declaration. It's been an obsolescent feature since
> 1989.

(On comp.lang.c, Tim Rentsch used it yesterday to make a point in a
thread also about forward declarations. I was quite surprised in what it
allowed you to do: suddenly C has turned into Python! Actually I think
it is useful for those of us who have trouble with function pointer syntax.)

> Why is being able to declare the same function more than once a problem?

(You mean a function pointer?)

Because it's wrong. In practically any other language, writing:

int a,a,a;

would be an error ('duplicate name 'a' or some such error). Even C
complains about duplicate function definitions, or typedefs or macros or
structs or labels. And it will complain about 'int a,a,a' if this is
inside a function. So why the special case at file-scope? Doubtless to
solve some problem with headers.

The trouble is it can hide some problems: you're maintaining someone
else's code which uses some vital global variable 'abc'.

You unknowingly choose the same name 'abc' for one or your own variables
at filescope (I think it needs to be the same type), and write to it in
your code. But now you've overwritten that important variable.

> The compiler checks that the declarations are consistent, and you don't
> *have* to repeat yourself. Nor do you have to write (******fooptr)(),
> any more than you have to write + + + + + + 42.

OK, so why can't I do this:

int *p;

int a = ***************p;

Because that /is/ picked by by the compiler.

It just makes nonsense of the type system. It's bad enough that there's
a discontinuity in the way that functions are called:

f(); // f is a function
g(); // g is a pointer to function
(*h)(); // h is a pointer to pointer to function
(**i)(); // i is a pointer to pointer to pointer to function

If there are N pointer levels, you need N-1 * symbols. Unless N is 0,
then just need N!

--
Bartc

Keith Thompson

unread,
Nov 16, 2015, 2:32:10 PM11/16/15
to
As I recall (I didn't study the code closely), someone had stated that
it's impossible to write mutually recursive functions without at least
one forward declaration; Tim's counterexample used an old-style function
type declaration. It wasn't really germane to the main point of the
discussion (and wasn't meant to be); a forward declaration is still the
best way to do that.

>> Why is being able to declare the same function more than once a problem?
>
> (You mean a function pointer?)

You're right, it's a repeated function pointer declaration. The same
would apply to an object declaration of any other type.

> Because it's wrong. In practically any other language, writing:
>
> int a,a,a;
>
> would be an error ('duplicate name 'a' or some such error). Even C
> complains about duplicate function definitions, or typedefs or macros or
> structs or labels. And it will complain about 'int a,a,a' if this is
> inside a function. So why the special case at file-scope? Doubtless to
> solve some problem with headers.
>
> The trouble is it can hide some problems: you're maintaining someone
> else's code which uses some vital global variable 'abc'.
>
> You unknowingly choose the same name 'abc' for one or your own variables
> at filescope (I think it needs to be the same type), and write to it in
> your code. But now you've overwritten that important variable.

Sure, it's a potential problem. I'm actually not sure of the reason
multiple identical object declarations at file scope are permitted; it's
a corner of the language I've never paid much attention to. But I don't
recall it ever being a problem in practice.

C has some misfeatures that exist mostly, or only, for backward
compatibility. If your argument is that C is imperfect, I don't think
you'll find anyone to take the other side of the debate.

>> The compiler checks that the declarations are consistent, and you don't
>> *have* to repeat yourself. Nor do you have to write (******fooptr)(),
>> any more than you have to write + + + + + + 42.
>
> OK, so why can't I do this:
>
> int *p;
>
> int a = ***************p;
>
> Because that /is/ picked by by the compiler.

I presume you're not saying you *want* to do that.

It's because the rule that applies to functions does not apply to ints.

An expression of function type is implicitly converted to a pointer
unless it's the operand of a unary sizeof or & operator. (`sizeof func`
is a constraint violation; `&func` yields the address of the function.)

A related but independent rule is that a parameter declaration of
function type is adjusted to the corresponding pointer-to-function type.

I am describing the rules, not defending them -- but that is the answer
to your question.

I'd probably have preferred it if the implicit conversion rule weren't
there, if the prefix of a function call had to be of function type, and
if a parameter declaration of function type were invalid rather than
being adjusted. Again, if you're arguing that C is flawed, you won't
find any opposition. I just don't think it's that big a deal.

> It just makes nonsense of the type system.

I think that's an absurd exaggeration. The type system is reasonably
consistent, with a handful of special-case rules that you have to keep
in mind.

> It just makes nonsense of the type system. It's bad enough that there's
> a discontinuity in the way that functions are called:
>
> f(); // f is a function
> g(); // g is a pointer to function
> (*h)(); // h is a pointer to pointer to function
> (**i)(); // i is a pointer to pointer to pointer to function

If you dislike the implicit conversion, you don't have to use it:

f(); // f is a function
(*g)(); // g is a pointer to function
(**h)(); // h is a pointer to pointer to function
...

It is perhaps unfortunate that a compiler will not enforce such a
convention for you. (It would make sense for a compiler to have an
option to warn about implicit function-to-pointer conversions, but it
would have to be disabled by default to allow for existing code.)

> If there are N pointer levels, you need N-1 * symbols. Unless N is 0,
> then just need N!

And I presume you now understand why, if you didn't already.

Nick Bowler

unread,
Nov 16, 2015, 3:18:59 PM11/16/15
to
On Mon, 16 Nov 2015 11:32:07 -0800, Keith Thompson wrote:
>>> As far as the function declaration with empty parentheses is concerned,
>>> that's an old-style declaration. It's been an obsolescent feature since
>>> 1989.

Curiously, there are functions which are only possible (here I go using
that word again, someone's going to prove me wrong :) to define using
old-style definition syntax.

There is some irony here, because this limitation of the new style
is caused by a declaration-before-use restriction in function parameters.
Unlike stuff at the top level, forward declarations are not permitted for
function parameters (as these identifiers have no linkage).

But old-style declarations are less restricted because they allow us to
re-order the declarations independently of the actual argument order:

/* Optional prototype. Yet another instance of redundant declarations */
int foo(int (*a)[*], int b);

int foo(a, b)
int b;
int (*a)[b];
{
/* ... */
}

In prototype style, the length argument (b) must precede the variably-
modified argument (a) in the parameter list.

>> (On comp.lang.c, Tim Rentsch used it yesterday to make a point in a
>> thread also about forward declarations. I was quite surprised in what it
>> allowed you to do: suddenly C has turned into Python! Actually I think
>> it is useful for those of us who have trouble with function pointer syntax.)
>
> As I recall (I didn't study the code closely), someone had stated that
> it's impossible to write mutually recursive functions without at least
> one forward declaration; Tim's counterexample used an old-style function
> type declaration. It wasn't really germane to the main point of the
> discussion (and wasn't meant to be); a forward declaration is still the
> best way to do that.

The old-style declaration was not important in Tim's example, it would
have also worked fine with prototypes (possibly with minor alterations).

Instead of a declaration of a function of type "T", he defined an
object of type "pointer to T", assigned to the object at runtime,
and called the function via that pointer. This approach makes the
forward declaration unneccesary, because the relevant calls do
not reference the function by its name.

I believe Tim was specifically responding to my use of the word
"impossible". He is right. It is not impossible, as demonstrated.

Kaz Kylheku

unread,
Nov 16, 2015, 3:21:54 PM11/16/15
to
On 2015-11-16, BartC <b...@freeuk.com> wrote:
> On 16/11/2015 10:49, Francis Glassborow wrote:
>
>> The only relaxation that I would even consider would be for static
>> functions. That might actually encourage more programmers to declare
>> their functions as static until they know they want to reuse them
>> elsewhere. But I am also of the school of thought that thinks variables
>> should be declared const until you find that you need to make them
>> mutable. And that style leads almost inevitably to a healthy
>> 'declare/define' one variable per declarative statement.
>>
>> Instead of trying to fix the language try fixing education. Of course
>> that is difficult but the rewards are high.
>
> That's a good point. You can make a language easier to teach by having
> fewer rules and quirks. But C has quirks by the bucket load!

Yes; not only does the adobe hut not have plumb walls and level floors,
it also lacks running water and electricity.

Getting walls plumb is easy, so let's argue about that.

James Kuyper

unread,
Nov 16, 2015, 3:48:57 PM11/16/15
to
On 11/16/2015 03:16 PM, Nick Bowler wrote:
> On Mon, 16 Nov 2015 11:32:07 -0800, Keith Thompson wrote:
>>>> As far as the function declaration with empty parentheses is concerned,
>>>> that's an old-style declaration. It's been an obsolescent feature since
>>>> 1989.
>
> Curiously, there are functions which are only possible (here I go using
> that word again, someone's going to prove me wrong :) to define using
> old-style definition syntax.

I'm going to prove you right. Any function that is supposed to either
return a pointer to a function of the same type, or take as an argument
a pointer to a function of the same type is an example. If you try to
declare one using pure function prototype syntax, you'll find yourself
trapped in an infinite recursion. You can escape the recursion at any
level by switching to using an old-style declaration instead. Examples:

int func0(int (*pf0)(/*old style*/));
int func1(int (*pf0)( int (*pf1)(/*old style*/) ));
int func2(int (*pf0)( int (*pf1)( int (*pf2)(/* old style */)) ));
...

Unfortunately, wherever you break the recursion, you disable prototype
checking at that level. Consider

int wrong0(void);
int wrong1(int (*pf1)(void));

func1(wrong0) is a constraint violation; func0(wrong0) is not.
func2(wrong1) is a constraint violation; func1(wrong1) is not.

Jakob Bohm

unread,
Nov 16, 2015, 4:01:23 PM11/16/15
to
The ability to declare an uninitialized variable multiple times is a
side effect of another feature (which I have actually found to be useful
in real, modern code):

The declaration

type foo; // At file scope, no explicit initilization

Really means the following, which cannot be expressed in any other way:

extern type foo;

#if !defined_and_initialized_in_any_file(foo)
type foo = (type)0; // Or = {0} depending on type
#endif

This is very useful for e.g. something like this

// In one file in a library:
void (*const g_pCallMeWheneverFooHappens)(void);

...

void dofoo(void) {
...
if (g_pCallMeWheneverFooHappens)
g_pCallMeWheneverFooHappens();
}

// In another file in that library
void CallMeWheneverFooHappens(void)
{
...
}

void (*const g_pCallMeWheneverFooHappens)(void) =
CallMeWheneverFooHappens;

In this example, if the library client references only the module with
dofoo(), the g_pCallMeWheneverFooHappens will be NULL and none of the
code will from the other module will be pulled in just because it wants
to known when foo happens (if it is linked in). If on the other hand
only the second module is referenced by the library client, then none
of the dofoo() code is linked in just because the module wants to do
something extra if dofoo() were ever to be called (which it wont since
it is not even linked in). Finally if both modules are referenced, the
explicit initialization in the second module wins over the default NULL
initialization, and the notification happens. And best of all, the
library client doesn't need to do anything special or keep track of how
the internal dependencies in the library implementation work.

Enjoy

Jakob
--
Jakob Bohm, CIO, Partner, WiseMo A/S. https://www.wisemo.com
Transformervej 29, 2860 Søborg, Denmark. Direct +45 31 13 16 10
This public discussion message is non-binding and may contain errors.
WiseMo - Remote Service Management for PCs, Phones and Embedded

Wojtek Lerch

unread,
Nov 16, 2015, 4:21:29 PM11/16/15
to
On 16-Nov-15 7:09 AM, BartC wrote:
> That's a good point. You can make a language easier to teach by having
> fewer rules and quirks. But C has quirks by the bucket load! The
> following is legal C for example:

It's "legal" in the sense that it doesn't trigger an obligatory
diagnostic, but at least two of the fooptr calls have undefined
behaviour. See 6.5.2.2#6.

> int (*fooptr)();
> int (*fooptr)();
> int (*fooptr)();
>
> int main(void){
>
> fooptr("one","two","three");
> fooptr(192.7,89.7);
> fooptr(&fooptr);
> (*******************fooptr)(&fooptr);
> }
>
> (Yes, you can redeclare the same thing over and over again;

What's wrong with redeclaring the same thing? The real problem with C
is that it makes it hard to tell a declaration from a definition.

> you can call a function declared with no parameters, with anything at all;

Not really, but you can try...

> Do we really have to teach all this stuff to a million students, or do
> we try and fix the language? (And do we also teach them how to use Cdecl?!)

There are many other languages that are much better for teaching
programming to beginners than C can ever hope to be. In fact, C has
never even cared about it. Allowing programmers to minimize the amount
of typing was a more important design goal of C than making the language
easy to understand or learn. Teaching students C as their first
language is a mistake.

Wojtek Lerch

unread,
Nov 16, 2015, 5:40:19 PM11/16/15
to
On 16-Nov-15 4:01 PM, Jakob Bohm wrote:
> On 16/11/2015 20:32, Keith Thompson wrote:
>> BartC <b...@freeuk.com> writes:
>>> On 16/11/2015 18:10, Keith Thompson wrote:
>>>> Why is being able to declare the same function more than once a
>>>> problem?
>>>
>>> (You mean a function pointer?)
>>
>> You're right, it's a repeated function pointer declaration. The same
>> would apply to an object declaration of any other type.
>>
>>> Because it's wrong. In practically any other language, writing:
>>>
>>> int a,a,a;
>>>
>>> would be an error ('duplicate name 'a' or some such error). Even C
>>> complains about duplicate function definitions, or typedefs or macros or
>>> structs or labels. And it will complain about 'int a,a,a' if this is
>>> inside a function. So why the special case at file-scope? Doubtless to
>>> solve some problem with headers.

The special case is called a "tentative definition" (6.9.2#2). I
couldn't find a mention of it in the Rationale, but I suspect it has
more to do with how compilers and linkers worked back in the eighties.

>>> The trouble is it can hide some problems: you're maintaining someone
>>> else's code which uses some vital global variable 'abc'.
>>>
>>> You unknowingly choose the same name 'abc' for one or your own variables
>>> at filescope (I think it needs to be the same type), and write to it in
>>> your code. But now you've overwritten that important variable.

Right. C is a hopeless mess when it comes to the notation that
determines what's a declaration and what's a definition and what its
linkage is. If the rules were saner, you would and up creating a
program with two definitions of the same symbol, causing a linker error.

>> Sure, it's a potential problem. I'm actually not sure of the reason
>> multiple identical object declarations at file scope are permitted; it's
>> a corner of the language I've never paid much attention to. But I don't
>> recall it ever being a problem in practice.

There's no reason why multiple declarations couldn't be allowed, if
there were a rule requiring exactly one definition. The problem is that
a "tentative definition" is one or the other, depending on the what else
you have in distant corners of the translation unit.

>> C has some misfeatures that exist mostly, or only, for backward
>> compatibility. If your argument is that C is imperfect, I don't think
>> you'll find anyone to take the other side of the debate.

That's a nice way of putting it. :)

> The ability to declare an uninitialized variable multiple times is a
> side effect of another feature (which I have actually found to be useful
> in real, modern code):
>
> The declaration
>
> type foo; // At file scope, no explicit initilization
>
> Really means the following, which cannot be expressed in any other way:
>
> extern type foo;
>
> #if !defined_and_initialized_in_any_file(foo)
> type foo = (type)0; // Or = {0} depending on type
> #endif

That would be useful, if it were true. But in reality it's #if
!defined_and_initialized_in_the_same_translation_unit(foo):

6.9.2#2 "A declaration of an identifier for an object that has file
scope without an initializer, and without a storage-class specifier or
with the storage-class specifier static, constitutes a /tentative
definition/. If a translation unit contains one or more tentative
definitions for an identifier, and the translation unit contains no
external definition for that identifier, then the behavior is exactly as
if the translation unit contains a file scope declaration of that
identifier, with the composite type as of the end of the translation
unit, with an initializer equal to 0."


> This is very useful for e.g. something like this
>
> // In one file in a library:
> void (*const g_pCallMeWheneverFooHappens)(void);
>
> ...
>
> void dofoo(void) {
> ...
> if (g_pCallMeWheneverFooHappens)
> g_pCallMeWheneverFooHappens();
> }
>
> // In another file in that library
> void CallMeWheneverFooHappens(void)
> {
> ...
> }
>
> void (*const g_pCallMeWheneverFooHappens)(void) =
> CallMeWheneverFooHappens;

I don't necessarily disagree that it's useful when it works, but
unfortunately it's undefined behaviour. You have two definitions of the
same symbol.

6.8#5 "If an identifier declare with external linkage is used in an
expression (other than as part of the operand of a sizeof or _Alignof
operator whose result is an integer constant), somewhere in the entire
program there shall be exactly one external definition for the
identifier; otherwise, there shall be no more than one."

Hans-Bernhard Bröker

unread,
Nov 17, 2015, 4:57:35 AM11/17/15
to
Am 16.11.2015 um 23:40 schrieb Wojtek Lerch:

> The special case is called a "tentative definition" (6.9.2#2). I
> couldn't find a mention of it in the Rationale, but I suspect it has
> more to do with how compilers and linkers worked back in the eighties.

Not really. Tentative definition, as actually specified in the C
Standards, only covers the scope of a single translation unit. The
linker doesn't get involved.

What you were probably getting at is the "common extensions" section,
where you'll find that on some implementations the linker expand the
tentative definition mechanism across the entire program. Those "some
implementations" include the majority of UNIX tool chains, which did
(and still do, AFAIK) this to allow cross-linking with FORTRAN,
particularly its dreaded COMMON blocks. GCC at least has -fno-common to
disable this.


Wojtek Lerch

unread,
Nov 17, 2015, 9:55:59 AM11/17/15
to
On 17-Nov-15 4:57 AM, Hans-Bernhard Bröker wrote:
> Am 16.11.2015 um 23:40 schrieb Wojtek Lerch:
>
>> The special case is called a "tentative definition" (6.9.2#2). I
>> couldn't find a mention of it in the Rationale, but I suspect it has
>> more to do with how compilers and linkers worked back in the eighties.
>
> Not really. Tentative definition, as actually specified in the C
> Standards, only covers the scope of a single translation unit. The
> linker doesn't get involved.

Not really. The linker doesn't get mentioned, but is involved.

> What you were probably getting at is the "common extensions" section,
> where you'll find that on some implementations the linker expand the
> tentative definition mechanism across the entire program.

Right. That's where the linker gets involved.

The compiler marks each tentative definition as a "common block" for the
linker. The linker counts how many "common blocks" and how many regular
definitions with the same name the program has, and decides to either
merge them into one or issue a duplicate symbol error. As far as the
Standard is concerned, any such merging of multiple definitions is
undefined behaviour, but "some implementations" offer it as an extension.

> Those "some
> implementations" include the majority of UNIX tool chains, which did
> (and still do, AFAIK) this to allow cross-linking with FORTRAN,
> particularly its dreaded COMMON blocks. GCC at least has -fno-common to
> disable this.

Right. And that's why the C standard was written, back in the eighties,
to allow but not require such behaviour.

Jakob Bohm

unread,
Nov 17, 2015, 11:14:46 AM11/17/15
to
Interestingly, this common block feature of linkers is now being used a
lot for the following C and C++ features:

- out-of-line instantiations of non-static inline functions. By
putting those in common blocks (one each), the linker will keep only
one, and as long as the definitions don't disagree among compilation
units that need (and thus generate) an out-of-line form of the
function, this is perfectly safe. Because optimizers might assign
registers and reorder operators differently, the linker cannot check
if the copies are byte-for-byte identical.

- string constant pooling. Each string constant "abc" etc. which is
not cast to a non-const char array is put in a common block named
after the string contents. The compiler generated naming ensures the
blocks are identical and the merging done by the linker then
eliminates (pools) identical strings across the compilation units.

- C++ template instantiations, handled similarly to out-of-line
instantiations of non-static inline functions in C.

- However the traditional "tentative definition" syntax remains the
only mechanism for accessing the "common block" feature directly using
standard language features (even though implementations are permitted
not to provide this behavior).

Keith Thompson

unread,
Nov 17, 2015, 12:02:15 PM11/17/15
to
Jakob Bohm <jb-u...@wisemo.com> writes:
[...]
> - string constant pooling. Each string constant "abc" etc. which is
> not cast to a non-const char array is put in a common block named
> after the string contents. The compiler generated naming ensures the
> blocks are identical and the merging done by the linker then
> eliminates (pools) identical strings across the compilation units.
[...]

I presume you mean "not used to initialize a non-const char array".
Casting a string literal to char*, for example, doesn't mean that the
array corresponding to the string literal may be updated. But for
something like

char s[] = "hello";

s may be modified. (Strictly speaking the static array corresponding to
the string literal is copied to s, but that can be optimized away.)

Wojtek Lerch

unread,
Nov 17, 2015, 1:32:22 PM11/17/15
to
On 17-Nov-15 12:02 PM, Keith Thompson wrote:
> [...] But for something like
>
> char s[] = "hello";
>
> s may be modified. (Strictly speaking the static array corresponding to
> the string literal is copied to s, but that can be optimized away.)

Even more strictly speaking, there is no static array corresponding to
the string literal. In the Standard's own words, "Successive characters
of the character string literal (including the terminating null
character if there is room or if the array is of unknown size)
initialize the elements of the array." (C99 6.7.8#14; similar words in
C11 6.7.9#14)

James Kuyper

unread,
Nov 17, 2015, 3:07:47 PM11/17/15
to
On 11/17/2015 01:32 PM, Wojtek Lerch wrote:
> On 17-Nov-15 12:02 PM, Keith Thompson wrote:
>> [...] But for something like
>>
>> char s[] = "hello";
>>
>> s may be modified. (Strictly speaking the static array corresponding to
>> the string literal is copied to s, but that can be optimized away.)
>
> Even more strictly speaking, there is no static array corresponding to
> the string literal. ...

"In translation phase 7, a byte or code of value zero is appended to
each multibyte character sequence that results from a string literal or
literals.78) The multibyte character sequence is then used to initialize
an array of static storage duration and length just
sufficient to contain the sequence." (6.4.5p6)

Now, since this is translation phase 7, you might reasonably think that
the array it's talking about is not a runtime array, but something that
only exists during translation of the program. However, the immediately
following clause says something inconsistent with that interpretation:
"If the program attempts to modify such an array, the behavior is
undefined." It's only possible for the program to attempt to modify such
an array if it's an array at runtime.

"An lvalue is an expression (with an object type other than void) that
potentially designates an object;64) if an lvalue does not designate an
object when it is evaluated, the behavior is undefined." (6.3.2.1p1)
"A string literal is ... an lvalue with type as detailed in 6.4.5."
(6.5.1p4).

The object that a string literal designates is the array described in
6.4.5p6.

Note that none of the clauses I've cited distinguishes between a string
literal used to initialize an array and other uses of string literals.
They apply just as much to array initializers as they do to other string
literals.

Since there's no way for a strictly conforming program to detect a
failure of an implementation to set aside memory to store such an array
when a string literal is used as an initializer, removal of that array
is always permitted according to the as-if rule - but as Keith said
"strictly speaking" there is supposed to be an array referred to by any
string literal, even the ones used as initializers.

> ... In the Standard's own words, "Successive characters
> of the character string literal (including the terminating null
> character if there is room or if the array is of unknown size)
> initialize the elements of the array." (C99 6.7.8#14; similar words in
> C11 6.7.9#14)

True, but irrelevant; that statement does not nullify 6.4.5p6.
--
James Kuyper

Wojtek Lerch

unread,
Nov 17, 2015, 3:44:50 PM11/17/15
to
On 17-Nov-15 3:07 PM, James Kuyper wrote:
> On 11/17/2015 01:32 PM, Wojtek Lerch wrote:
>> On 17-Nov-15 12:02 PM, Keith Thompson wrote:
...
>>> char s[] = "hello";
...
>> ... In the Standard's own words, "Successive characters
>> of the character string literal (including the terminating null
>> character if there is room or if the array is of unknown size)
>> initialize the elements of the array." (C99 6.7.8#14; similar words in
>> C11 6.7.9#14)
>
> True, but irrelevant; that statement does not nullify 6.4.5p6.

Yeah, you're right, after a closer look I noticed that too. I could
argue that the above quote talks about the characters of the token
(because that's what a string literal is) rather than the characters of
the static array that the token's characters are used to initialize; but
that does not change the fact that according to 6.4.5p6 such an array
does exist, even if it's not where the characters are copied from into
our s. But the Standard is not nearly pedantic enough about this kind
of subtle terminological distinctions to make such hair splitting
meaningful.

Hans-Bernhard Bröker

unread,
Nov 17, 2015, 6:23:26 PM11/17/15
to
Am 17.11.2015 um 15:55 schrieb Wojtek Lerch:
> On 17-Nov-15 4:57 AM, Hans-Bernhard Bröker wrote:
>> Am 16.11.2015 um 23:40 schrieb Wojtek Lerch:
>>> The special case is called a "tentative definition" (6.9.2#2). I
>>> couldn't find a mention of it in the Rationale, but I suspect it has
>>> more to do with how compilers and linkers worked back in the eighties.
>>
>> Not really. Tentative definition, as actually specified in the C
>> Standards, only covers the scope of a single translation unit. The
>> linker doesn't get involved.
>
> Not really. The linker doesn't get mentioned, but is involved.

No, it's really not. The linker has no role in the standard tentative
definition mechanism. That takes place entirely inside one translation
unit.

The linker only gets to handle the end result, just as if the tentative
definition(s) had been a proper definition (plus some declarations) from
the start.

> The compiler marks each tentative definition as a "common block" for the
> linker.

But that applies only if the compiler is set up to support extension C99
J.5.11. A standard-conforming compiler is fully allowed to emit a
direct, non-shareable, i.e. non-"common" definition.

Nor does the extension only concern (former) tentative definitions. A
J.5.11 style compiler has to mark _every_ external variable definition
"common", and leave it to the linker to sort them out.

Wojtek Lerch

unread,
Nov 18, 2015, 2:14:29 AM11/18/15
to
On 17/11/2015 6:23 PM, Hans-Bernhard Bröker wrote:
> Am 17.11.2015 um 15:55 schrieb Wojtek Lerch:
>> On 17-Nov-15 4:57 AM, Hans-Bernhard Bröker wrote:
>>> Am 16.11.2015 um 23:40 schrieb Wojtek Lerch:
>>>> The special case is called a "tentative definition" (6.9.2#2). I
>>>> couldn't find a mention of it in the Rationale, but I suspect it has
>>>> more to do with how compilers and linkers worked back in the eighties.
>>>
>>> Not really. Tentative definition, as actually specified in the C
>>> Standards, only covers the scope of a single translation unit. The
>>> linker doesn't get involved.
>>
>> Not really. The linker doesn't get mentioned, but is involved.
>
> No, it's really not. The linker has no role in the standard tentative
> definition mechanism. That takes place entirely inside one translation
> unit.

I think this is a misunderstanding, not a disagreement.

Agreed: the linker does not have a role in the standard tentative
definition mechanism, other than its normal role in processing of the
results of all definition. And whatever role its implementer assigned
to it in situations that the standard considers undefined.

But the behaviour of linkers existing at the time did have a role in the
decisions that shaped the standard tentative definition mechanism --
that's all I meant when I mentioned the involvement of linkers. You may
remember that this subthread started as a discussion of the purpose of
the tentative definition mechanism in the standard.

> The linker only gets to handle the end result, just as if the tentative
> definition(s) had been a proper definition (plus some declarations) from
> the start.

Agreed. As far a the standard is concerned, there's no difference
between a translation unit that defines a variable tentatively, and a
similar translation unit that explicitly initializes the variable to
zero. (Extensions may treat them differently though.)

>> The compiler marks each tentative definition as a "common block" for the
>> linker.
>
> But that applies only if the compiler is set up to support extension C99
> J.5.11.

Or some similar extension. Yes, that was the context.

> A standard-conforming compiler is fully allowed to emit a
> direct, non-shareable, i.e. non-"common" definition.

Absolutely.

> Nor does the extension only concern (former) tentative definitions. A
> J.5.11 style compiler has to mark _every_ external variable definition
> "common", and leave it to the linker to sort them out.

Actually J.5.11 says "if the definitions disagree, or more than one is
initialized, the behavior is undefined". In other words, only one of
the definitions can have an initializer -- the rest have to be tentative.

But of course, J.5.11 is just a non-normative example; other
implementations could have an extension that works the way you describe.

Francis Glassborow

unread,
Nov 18, 2015, 7:03:52 AM11/18/15
to
Of course we don't. What we do need to teach all students is that just
getting code to compile is not enough. They need to write sane code to
start with. Were it not that it will break many millions of lines of
code (I real commercial consideration) I would remove the possibility of
declaring a function with an unspecified number of parameters. I do not
want to start a language war but that same code generates 8 errors when
compiled as C++.

The removal of implicit function declarations from C was not enough, at
the same time we should have removed declarations of functions with
unspecified parameters.

Francis

BartC

unread,
Nov 18, 2015, 8:16:33 AM11/18/15
to
On 18/11/2015 12:03, Francis Glassborow wrote:
> On 16/11/2015 12:09, BartC wrote:
>> On 16/11/2015 10:49, Francis Glassborow wrote:
>>
>>> The only relaxation that I would even consider would be for static
>>> functions. That might actually encourage more programmers to declare
>>> their functions as static until they know they want to reuse them
>>> elsewhere.

>>> Instead of trying to fix the language try fixing education. Of course
>>> that is difficult but the rewards are high.
>>
>> That's a good point. You can make a language easier to teach by having
>> fewer rules and quirks. But C has quirks by the bucket load!

>> Do we really have to teach all this stuff to a million students, or do
>> we try and fix the language? (And do we also teach them how to use
>> Cdecl?!)
>>
>
> Of course we don't. What we do need to teach all students is that just
> getting code to compile is not enough. They need to write sane code to
> start with. Were it not that it will break many millions of lines of
> code (I real commercial consideration) I would remove the possibility of
> declaring a function with an unspecified number of parameters. I do not
> want to start a language war but that same code generates 8 errors when
> compiled as C++.
>
> The removal of implicit function declarations from C was not enough, at
> the same time we should have removed declarations of functions with
> unspecified parameters.

OK, so you do agree that sometimes changing the language can be useful?

Well, some people think that making the small change so that code with
this kind of layout compiles properly:

void fna(void) { fnb("abc"); }

void fnb(char*) { }

is worth doing and means students can concentrate on more important
things. (Why shouldn't it work anyway; what exactly is the problem?)

>>> But I am also of the school of thought that thinks variables
>>> should be declared const until you find that you need to make them
>>> mutable. And that style leads almost inevitably to a healthy
>>> 'declare/define' one variable per declarative statement.

That style leads to code like this:

const int const * const p

because no one can remember exactly where const is supposed to go! So
they put it everywhere.

Actually I've seen code that does use const pretty much everywhere. You
just get so much clutter that you can hardly make out the code itself or
any real bugs that might be lurking.

(I think Pascal got this right: 'const' for actual constants (not
variables with a hint to the compiler that they ought not to be written
to), and 'var' for variables or for parameters that will be changed in
the caller. Very simple, solid concepts to learn.

C's 'const' on the other hand, taken together with #define and enum, is
just a mess that a lot of people can't get right either. We don't want
extra education here, but eradication.)

--
Bartc

Jakob Bohm

unread,
Nov 18, 2015, 10:20:46 AM11/18/15
to
On 17/11/2015 18:02, Keith Thompson wrote:
> Jakob Bohm <jb-u...@wisemo.com> writes:
> [...]
>> - string constant pooling. Each string constant "abc" etc. which is
>> not cast to a non-const char array is put in a common block named
>> after the string contents. The compiler generated naming ensures the
>> blocks are identical and the merging done by the linker then
>> eliminates (pools) identical strings across the compilation units.
> [...]
>
> I presume you mean "not used to initialize a non-const char array".
> Casting a string literal to char*, for example, doesn't mean that the
> array corresponding to the string literal may be updated.

I was simply trying to sidestep the possibility that some backwards
compatible compilers might exclude potentially written char literals
(not array initializers) from string pooling, because that was
irrelevant to my discussion of how linker common block handling is used
to implement pooling of string literals.


> ... (Snip discussion of array initializers written in string literal form)

Keith Thompson

unread,
Nov 18, 2015, 11:36:42 AM11/18/15
to
BartC <b...@freeuk.com> writes:
[...]
> That style leads to code like this:
>
> const int const * const p
>
> because no one can remember exactly where const is supposed to go! So
> they put it everywhere.
>
> Actually I've seen code that does use const pretty much everywhere. You
> just get so much clutter that you can hardly make out the code itself or
> any real bugs that might be lurking.
>
> (I think Pascal got this right: 'const' for actual constants (not
> variables with a hint to the compiler that they ought not to be written
> to), and 'var' for variables or for parameters that will be changed in
> the caller. Very simple, solid concepts to learn.
>
> C's 'const' on the other hand, taken together with #define and enum, is
> just a mess that a lot of people can't get right either. We don't want
> extra education here, but eradication.)

You propose removing the ability to assert that an object whose initial
value is computed at run time may not be modified. No thanks.

I suppose Pascal did get it right, but only in the sense that it was
optimized for simplicity rather than for usefulness in systems programming.

(Incidentally, I've mentioned a language that makes objects read-only by
default. It's Rust, which requires a `mut` keyword to enable
modification. It's far too late to make such a change to C, but I'd
like to see the idea spread to other new languages.)

I agree that C's declaration syntax is a bit of a mess. I *don't* agree
that it's as bad as you think it is. As far as education is concerned,
we should acknowledge that it could have been done more cleanly, but
we're stuck with it. The cdecl program is helpful; I use it myself.
New languages shouldn't repeat C's mistakes (even the minor ones).
It is loading more messages.
0 new messages