Discussion:
A Standard Proposal: Remove forward declaration requirements
(too old to reply)
Rick C. Hodgin
2017-07-26 19:42:11 UTC
Permalink
Raw Message
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.

-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?

And just to be clear, I'm not saying we should remove the forward-
declaration ability altogether, but just add a new option which
removes the requirement of having it.

I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.

Thank you,
Rick C. Hodgin
David Kleinecke
2017-07-26 20:20:34 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
And just to be clear, I'm not saying we should remove the forward-
declaration ability altogether, but just add a new option which
removes the requirement of having it.
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
On the whole I agree - but this comes close to the de
minibus we are not supposed to curate.

I would, indeed, like to present my translation units in
top-down fashion. The exported functions first followed
by the static functions the exported functions call.

But it hardly difficult (and possibly useful) to do a
"table of contents" of forward declarations at the start
of the unit. Thereafter I can present the functions in
any order I like.

I don't think abolishing the forward declaration
requirement would speed up coding appreciably though.
All it would do is make source code a bit easier to read.
Jens Thoms Toerring
2017-07-26 20:55:58 UTC
Permalink
Raw Message
Post by David Kleinecke
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
I don't think abolishing the forward declaration
requirement would speed up coding appreciably though.
All it would do is make source code a bit easier to read.
All it would do is to make it IMPOSSIBLE for the compiler to
check function calls for correctness concerning the number of
arguments passed and their types. The costs would be huge
since such errors in the code wouldn't be flagged by the com-
piler anymore. Instead they would result in weird behaviour of
the program when a function gets called with unsuitable argu-
ments. That might go unnoticed for quite some time and, once
noticed, these bugs can only be found by debugging, which can
be hard enough on a normal PC and often not even possible when
the program runs e.g. on a microcontroller (where you have no
operating system and your buggy program is all what amounts to
an OS). I've hardly ever seen a more completely stupid proposal
(at least in this group). What next? Remove the requirement to
declare a variable before its use?

Regards, Jens
--
\ Jens Thoms Toerring ___ ***@toerring.de
\__________________________ http://toerring.de
bartc
2017-07-26 21:20:28 UTC
Permalink
Raw Message
Post by Jens Thoms Toerring
Post by David Kleinecke
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
I don't think abolishing the forward declaration
requirement would speed up coding appreciably though.
All it would do is make source code a bit easier to read.
All it would do is to make it IMPOSSIBLE for the compiler to
check function calls for correctness concerning the number of
arguments passed and their types.
I don't get you; why won't the compiler know the types?

Here:

fn("cat");

....

void fn(float) {...}

it will know a string is an unsuitable argument because the declaration
of fn is visible later on.

It just means it can't start to do type-checking until it's done a pass
over the source.
Post by Jens Thoms Toerring
The costs would be huge
since such errors in the code wouldn't be flagged by the com-
piler anymore.
Nonsense.

What you will get is complete freedom in placement of local functions,
and no need to maintain a corresponding bunch of forward declarations at
the start of the file.
--
bartc
jacobnavia
2017-07-26 21:32:15 UTC
Permalink
Raw Message
Post by Jens Thoms Toerring
All it would do is to make it IMPOSSIBLE for the compiler to
check function calls for correctness concerning the number of
arguments passed and their types.
Surely not.

1) The compiler sees a function call with no previous declaration.
2) It saves it in the "Undeclared" list
3) The compiler sees a function declaration.
4) It checks if it is in the undeclared list and
4) Verifies all arguments to the function in all previous calls.

I can't see why this would be impossible to do.

Lcc doesn't do this, it is a one pass compiler. It wouldn't be so
difficult to add but why would you really want to do that?

It is not completely ridiculous and helps the "preacher" prove that he
doesn't always post off topic nonsense.

Any undefined function calls would be flagged as an error at the end of
the compilation unit. The declaration of the call must appear in the
same compilation unit.

That would work but is it really necessary? Reading C is easy now since
you know that the declaration of the function is before the current
line. With that innovation, we would not know where to look, forwards or
backwards.

A formal table of contents before the file proper helps to set the
context. It is easier to read. Yes, compilers can do much more
complicated things than that, but do our brains+ follow? Because code
must be read by humans, and programs with a table of contents are easier
on the mind.
Rick C. Hodgin
2017-07-26 21:58:35 UTC
Permalink
Raw Message
Post by jacobnavia
Post by Jens Thoms Toerring
All it would do is to make it IMPOSSIBLE for the compiler to
check function calls for correctness concerning the number of
arguments passed and their types.
Surely not.
1) The compiler sees a function call with no previous declaration.
2) It saves it in the "Undeclared" list
3) The compiler sees a function declaration.
4) It checks if it is in the undeclared list and
4) Verifies all arguments to the function in all previous calls.
I can't see why this would be impossible to do.
Lcc doesn't do this, it is a one pass compiler. It wouldn't be so
difficult to add but why would you really want to do that?
The IDEs I use have built-in functions which take you to both the
declarations and body definitions of the things you're referencing,
be they functions or structs or classes or defines or whatever.

In the IDE I use most often (Visual Studio) there is a window called
a Code Window, which is constantly updating itself contextually with
whatever the caret happens to be on. As such, when I move over the
top of a token name I see its declaration or definition. A couple
keystrokes and it takes me there. So the navigation issue isn't
really the issue.

For me, the bulk of the effort would come from not needing to have
things loaded in a correct order. So they just need to be loaded,
and then the compiler will resolve the references to where they
actually go.
Post by jacobnavia
That would work but is it really necessary? Reading C is easy now since
you know that the declaration of the function is before the current
line. With that innovation, we would not know where to look, forwards or
backwards.
It would break backward compatibility with existing tools which do
follow the C-style top-down parsing of source code, but would allow
those tools to be extended, or other tools to be created, which are
able to parse the source in this manner.

The tool I have now already does this, and it is a tremendous asset
because there are times I code functions deep in the c/cpp body, and
do not yet add them to the header file, yet I am still able to use
the navigation features to go to the body, and also to find their
usages in source code, because that tool is not limited to a top-
down parse.

The tool I use is a Visual Studio add-on by Whole Tomato called
Visual Assist X. It's worth its weight in gold.
Post by jacobnavia
A formal table of contents before the file proper helps to set the
context. It is easier to read. Yes, compilers can do much more
complicated things than that, but do our brains+ follow? Because code
must be read by humans, and programs with a table of contents are easier
on the mind.
I've had people tell me that at various times, and I tend to disagree
with that assessment. I would rather see main() at the top, and then
if I wanted to dive into all the details, to dig further down in the
source code. But, that's personal preference.

Thank you,
Rick C. Hodgin
s***@casperkitty.com
2017-07-26 22:20:06 UTC
Permalink
Raw Message
Post by jacobnavia
That would work but is it really necessary? Reading C is easy now since
you know that the declaration of the function is before the current
line. With that innovation, we would not know where to look, forwards or
backwards.
Given some of the weirdness in the syntax, I wouldn't say that reading C
is "easy", but there are at least a couple of places where deviating from
declaration order would be helpful:

1. Allow a function to return a pointer to a function of its own type,
allowing code like:

processFunc = processFunc(inputDat);

without having to add type conversions or wrapper structs.

2. Allow array-sizes parameters to be used within array parameter
definitions without having to up-end the convention of passing
pointers followed by sizes. Is anything really gained by saying
that someone wanting to let a compiler pre-fetch values from a
source array for a function whose prototype is:

float computate_stuff(float dat[restrict], int count);

should write that as:

static void do_compute_stuff(int count, float dat[restrict static count])
... body goes here

float compute_stuff(float dat[restrict], int count)
{
do_compute_stuff(count, dat);
}

To be sure, the first could be resolved by allowing a typedef for an
incomplete function-pointer type, and the second by having a compiler
make two passes through parameter lists--one to identify variable names
and one to look at array bounds.
David Brown
2017-07-27 09:06:45 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
Post by jacobnavia
That would work but is it really necessary? Reading C is easy now since
you know that the declaration of the function is before the current
line. With that innovation, we would not know where to look, forwards or
backwards.
Given some of the weirdness in the syntax, I wouldn't say that reading C
is "easy", but there are at least a couple of places where deviating from
1. Allow a function to return a pointer to a function of its own type,
processFunc = processFunc(inputDat);
without having to add type conversions or wrapper structs.
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful - and far
more difficulty imaging a situation when you couldn't just put a quick
typedef of the function type in the code.
Post by s***@casperkitty.com
2. Allow array-sizes parameters to be used within array parameter
definitions without having to up-end the convention of passing
pointers followed by sizes. Is anything really gained by saying
that someone wanting to let a compiler pre-fetch values from a
float computate_stuff(float dat[restrict], int count);
static void do_compute_stuff(int count, float dat[restrict static count])
... body goes here
float compute_stuff(float dat[restrict], int count)
{
do_compute_stuff(count, dat);
}
To be sure, the first could be resolved by allowing a typedef for an
incomplete function-pointer type, and the second by having a compiler
make two passes through parameter lists--one to identify variable names
and one to look at array bounds.
Making array parameters work sensibly, with compiler checking, would be
a good thing for C.
Ben Bacarisse
2017-07-27 10:55:48 UTC
Permalink
Raw Message
<snip>
Post by David Brown
Post by s***@casperkitty.com
1. Allow a function to return a pointer to a function of its own type,
processFunc = processFunc(inputDat);
without having to add type conversions or wrapper structs.
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
It's hardly common, I agree, but I've done this (more than once I think)
when implementing a state machine as functions rather than, say, a giant
case statement.
Post by David Brown
- and far
more difficulty imaging a situation when you couldn't just put a quick
typedef of the function type in the code.
I'm not sure what you mean here.

<snip>
--
Ben.
David Brown
2017-07-27 11:39:40 UTC
Permalink
Raw Message
Post by Ben Bacarisse
<snip>
Post by David Brown
Post by s***@casperkitty.com
1. Allow a function to return a pointer to a function of its own type,
processFunc = processFunc(inputDat);
without having to add type conversions or wrapper structs.
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
It's hardly common, I agree, but I've done this (more than once I think)
when implementing a state machine as functions rather than, say, a giant
case statement.
Post by David Brown
- and far
more difficulty imaging a situation when you couldn't just put a quick
typedef of the function type in the code.
I'm not sure what you mean here.
When trying to write an example, the point has just dawned on me - you'd
need a recursive typedef of some kind.

I guess C++14 function return type deduction is the answer here (if you
don't want the conversions or wrapper structs, as Supercat said).
James R. Kuyper
2017-07-27 17:39:36 UTC
Permalink
Raw Message
On 07/27/2017 07:39 AM, David Brown wrote:
...
...
Post by David Brown
Post by David Brown
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
...
Post by David Brown
When trying to write an example, the point has just dawned on me - you'd
need a recursive typedef of some kind.
I guess C++14 function return type deduction is the answer here (if you
don't want the conversions or wrapper structs, as Supercat said).
A fully-prototyped declaration of a function that either returns a
pointer to a function of it's own type, or takes an argument which is
such a pointer, would necessarily involve infinite recursion. You can
approximate such a type declaration in C by terminating the recursion
using a non-prototype function declaration. However, C++ doesn't have
non-prototype function declarations, so the type declaration would
necessarily be infinitely long. I don't know the details of how C++14
defines "return type deduction", but I suspect that it MUST fail in such
a case.
David Brown
2017-07-28 09:18:20 UTC
Permalink
Raw Message
Post by James R. Kuyper
...
...
Post by David Brown
Post by David Brown
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
...
Post by David Brown
When trying to write an example, the point has just dawned on me - you'd
need a recursive typedef of some kind.
I guess C++14 function return type deduction is the answer here (if you
don't want the conversions or wrapper structs, as Supercat said).
A fully-prototyped declaration of a function that either returns a
pointer to a function of it's own type, or takes an argument which is
such a pointer, would necessarily involve infinite recursion. You can
approximate such a type declaration in C by terminating the recursion
using a non-prototype function declaration. However, C++ doesn't have
non-prototype function declarations, so the type declaration would
necessarily be infinitely long. I don't know the details of how C++14
defines "return type deduction", but I suspect that it MUST fail in such
a case.
A brief test (because it is easier than reading the standards, even if
the answer is not necessarily definitive) shows you are right:

auto foo(void) {
return &foo;
}

<source>: In function 'auto foo()':
3 : <source>:3:13: error: use of 'auto foo()' before deduction of 'auto'
return &foo;
^~~
Compiler exited with result code 1


I had a little go at trying to make a C++ class instead of a struct
wrapper, in the hope that conversion operators could be written to avoid
the casts needed in an example like Jacob's in the "State machine using
function pointers" threads. But I get the same recursion problems.
Maybe it is possible, but beyond my C++ skills. Even it is possible, it
will still rely on an implementation-dependent conversion between two
function pointer types AFAICS.
Ben Bacarisse
2017-07-28 16:10:38 UTC
Permalink
Raw Message
Post by David Brown
Post by James R. Kuyper
...
...
Post by David Brown
Post by David Brown
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
...
Post by David Brown
When trying to write an example, the point has just dawned on me - you'd
need a recursive typedef of some kind.
I guess C++14 function return type deduction is the answer here (if you
don't want the conversions or wrapper structs, as Supercat said).
A fully-prototyped declaration of a function that either returns a
pointer to a function of it's own type, or takes an argument which is
such a pointer, would necessarily involve infinite recursion. You can
approximate such a type declaration in C by terminating the recursion
using a non-prototype function declaration. However, C++ doesn't have
non-prototype function declarations, so the type declaration would
necessarily be infinitely long. I don't know the details of how C++14
defines "return type deduction", but I suspect that it MUST fail in such
a case.
A brief test (because it is easier than reading the standards, even if
auto foo(void) {
return &foo;
}
3 : <source>:3:13: error: use of 'auto foo()' before deduction of 'auto'
return &foo;
^~~
Compiler exited with result code 1
I had a little go at trying to make a C++ class instead of a struct
wrapper, in the hope that conversion operators could be written to avoid
the casts needed in an example like Jacob's in the "State machine using
function pointers" threads. But I get the same recursion problems.
Maybe it is possible, but beyond my C++ skills. Even it is possible, it
will still rely on an implementation-dependent conversion between two
function pointer types AFAICS.
In C at least I don't think the conversion is implementation defined.
It's well-defined by the standard provided the converted pointer
actually points to a function of the right type and it is called with
suitable arguments. There are only options: well-defined or undefined.
--
Ben.
Ben Bacarisse
2017-07-27 21:00:56 UTC
Permalink
Raw Message
Post by David Brown
Post by Ben Bacarisse
<snip>
Post by David Brown
Post by s***@casperkitty.com
1. Allow a function to return a pointer to a function of its own type,
processFunc = processFunc(inputDat);
without having to add type conversions or wrapper structs.
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
It's hardly common, I agree, but I've done this (more than once I think)
when implementing a state machine as functions rather than, say, a giant
case statement.
Post by David Brown
- and far
more difficulty imaging a situation when you couldn't just put a quick
typedef of the function type in the code.
I'm not sure what you mean here.
When trying to write an example, the point has just dawned on me - you'd
need a recursive typedef of some kind.
I guess C++14 function return type deduction is the answer here
I don't think that's possible (though I'd love to be wrong about
this!). I can't see how the type deduction could get started. One of
these functions must return something whose type does not depend on
something not yet deduced.

I'm not saying it's not theoretically possible (the deduced type would
be some kind of fixed-point), just that I don't think C++'s type
deduction is of the sort that could do it. Of course, I might be wrong
about that too and it's not even theoretically possible, but I'd enjoy
being wrong about that too!
--
Ben.
Tim Rentsch
2017-07-28 09:04:23 UTC
Permalink
Raw Message
Post by Ben Bacarisse
Post by David Brown
Post by Ben Bacarisse
<snip>
Post by David Brown
1. Allow a function to return a pointer to a function of its own type,
processFunc = processFunc(inputDat);
without having to add type conversions or wrapper structs.
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
It's hardly common, I agree, but I've done this (more than once I think)
when implementing a state machine as functions rather than, say, a giant
case statement.
Post by David Brown
- and far
more difficulty imaging a situation when you couldn't just put a quick
typedef of the function type in the code.
I'm not sure what you mean here.
When trying to write an example, the point has just dawned on me - you'd
need a recursive typedef of some kind.
I guess C++14 function return type deduction is the answer here
I don't think that's possible (though I'd love to be wrong about
this!). I can't see how the type deduction could get started. One of
these functions must return something whose type does not depend on
something not yet deduced.
I'm not saying its not theoretically possible (the deduced type would
be some kind of fixed-point), just that I don't think C++'s type
deduction is of the sort that could do it. [...]
Is there such a type within the existing C++ type space (or the C
type space for that matter)? AFAICT there is not. If there is
no type that would work then no inference system would be able
to deduce one. (Proof: exercise for the reader)
Ben Bacarisse
2017-07-28 11:08:14 UTC
Permalink
Raw Message
Post by Tim Rentsch
Post by Ben Bacarisse
Post by David Brown
Post by Ben Bacarisse
<snip>
Post by David Brown
1. Allow a function to return a pointer to a function of its own type,
processFunc = processFunc(inputDat);
without having to add type conversions or wrapper structs.
How often is /that/ going to be useful? A function that returns a
pointer to a function of its own type? Maybe I'm missing something, but
I have a lot of difficulty imagining when that might be useful
It's hardly common, I agree, but I've done this (more than once I think)
when implementing a state machine as functions rather than, say, a giant
case statement.
Post by David Brown
- and far
more difficulty imaging a situation when you couldn't just put a quick
typedef of the function type in the code.
I'm not sure what you mean here.
When trying to write an example, the point has just dawned on me - you'd
need a recursive typedef of some kind.
I guess C++14 function return type deduction is the answer here
I don't think that's possible (though I'd love to be wrong about
this!). I can't see how the type deduction could get started. One of
these functions must return something whose type does not depend on
something not yet deduced.
I'm not saying its not theoretically possible (the deduced type would
be some kind of fixed-point), just that I don't think C++'s type
deduction is of the sort that could do it. [...]
Is there such a type within the existing C++ type space (or the C
type space for that matter)? AFAICT there is not. If there is
no type that would work then no inference system would be able
to deduce one. (Proof: exercise for the reader)
No, there isn't. The second paragraph was supposed to be about types
and languages in general. Saying that C++'s type *deduction* mechanism
can't do it was way too specific. I should have said that C++'s type
system (which only implicitly refer to deduction) can't do it.
--
Ben.
bartc
2017-07-26 22:22:58 UTC
Permalink
Raw Message
Post by jacobnavia
A formal table of contents before the file proper helps to set the
context. It is easier to read. Yes, compilers can do much more
complicated things than that, but do our brains+ follow? Because code
must be read by humans, and programs with a table of contents are easier
on the mind.
Yet when it comes to a 'table of contents' at the start of a function,
setting out all the local variables, people here prefer them to be
strewn all over the function instead, even re-using the same names in
different blocks.

And a function body is a piece of code you do need to read and work
through as a coherent sequence to understand it. Unlike the 100 separate
functions in a file, where the order should not matter as there is no
linear flow of control from the end of one function to the next in the
file. (And where a table of contents can be easily generated by an IDE.)
--
bartc
David Brown
2017-07-27 11:44:56 UTC
Permalink
Raw Message
Post by bartc
Post by jacobnavia
A formal table of contents before the file proper helps to set the
context. It is easier to read. Yes, compilers can do much more
complicated things than that, but do our brains+ follow? Because code
must be read by humans, and programs with a table of contents are
easier on the mind.
Yet when it comes to a 'table of contents' at the start of a function,
setting out all the local variables, people here prefer them to be
strewn all over the function instead, even re-using the same names in
different blocks.
You use "people here" as though it applied to everyone in this group (it
does not), and not to other C programmers (defining variables when you
need them and can initialise them is very common amongst programmers
outside of c.l.c.).

Yes, I declare variables when I am ready to use them, and not before.
That is not "strewn all over the function" - it is isolating them to
where they are relevant to the code.

And I don't keep a "table of contents" list of functions in my files. I
see no point in that. If such a table were useful, the file is too big.
Post by bartc
And a function body is a piece of code you do need to read and work
through as a coherent sequence to understand it. Unlike the 100 separate
functions in a file, where the order should not matter as there is no
linear flow of control from the end of one function to the next in the
file. (And where a table of contents can be easily generated by an IDE.)
If you think the order of the functions in a file does not matter, then
what is the problem with putting them in callee-caller order?
Malcolm McLean
2017-07-27 11:58:10 UTC
Permalink
Raw Message
Post by David Brown
And I don't keep a "table of contents" list of functions in my files. I
see no point in that. If such a table were useful, the file is too big.
I do, for example I've got a file called BezSup.cpp (It's actually a C++ file
but quite C-like). It contains quite a large number of basic functions which
operate on BezierCurves.
Now BezSup.h is effectively a list of contents. It's tagged with Doxygen comments,
and Doxygen generates an HTML-browseable list of functions in that file. There
aren't many static functions in this particular file, but those there are are also
listed at the top of the cpp file.

It's useful to have an overview of what's in a particular file.
David Brown
2017-07-27 13:14:49 UTC
Permalink
Raw Message
Post by Malcolm McLean
Post by David Brown
And I don't keep a "table of contents" list of functions in my files. I
see no point in that. If such a table were useful, the file is too big.
I do, for example I've got a file called BezSup.cpp (It's actually a C++ file
but quite C-like). It contains quite a large number of basic functions which
operate on BezierCurves.
Now BezSup.h is effectively a list of contents. It's tagged with Doxygen comments,
and Doxygen generates an HTML-browseable list of functions in that file. There
aren't many static functions in this particular file, but those there are are also
listed at the top of the cpp file.
It's useful to have an overview of what's in a particular file.
That is a different matter entirely. Your header here, BezSup.h,
contains extern declarations of the functions that your module
BezSup.cpp exports (along with any types, constants, global variables,
etc.). This header servers an essential purpose, providing the public
API for the code - along with documentation for that API.

I don't think anyone is considering eliminating that sort of thing.

And I don't consider such a header to be a "table of contents". In
particular, I would not necessarily expect the order of functions in the
header to match the order of functions in the implementation. In the
header file, you order them according to your desires for API
documentation - perhaps in groups that are logically related, or perhaps
alphabetically, or perhaps with the most used functions first. In the
implementation file, you may have a totally different order.


What we are talking about here is for /implementation/ functions -
static functions within the BezSup.cpp file that are not accessible from
outside.
bartc
2017-07-27 12:01:27 UTC
Permalink
Raw Message
Post by David Brown
Post by bartc
And a function body is a piece of code you do need to read and work
through as a coherent sequence to understand it. Unlike the 100 separate
functions in a file, where the order should not matter as there is no
linear flow of control from the end of one function to the next in the
file. (And where a table of contents can be easily generated by an IDE.)
If you think the order of the functions in a file does not matter, then
what is the problem with putting them in callee-caller order?
The problem is having to put them in callee-caller order.

There might be a preferred order (such as having the most commonly
accessed functions near beginning and end for faster navigation), but
that needn't have anything to do with the call-dependency pattern
between functions.
--
bartc
David Brown
2017-07-27 13:17:47 UTC
Permalink
Raw Message
Post by bartc
Post by David Brown
Post by bartc
And a function body is a piece of code you do need to read and work
through as a coherent sequence to understand it. Unlike the 100 separate
functions in a file, where the order should not matter as there is no
linear flow of control from the end of one function to the next in the
file. (And where a table of contents can be easily generated by an IDE.)
If you think the order of the functions in a file does not matter, then
what is the problem with putting them in callee-caller order?
The problem is having to put them in callee-caller order.
It is no problem.

You have to put them in /some/ order - one function has to come before
the other. Why should it be such a problem to put the callee first?
Post by bartc
There might be a preferred order (such as having the most commonly
accessed functions near beginning and end for faster navigation), but
that needn't have anything to do with the call-dependency pattern
between functions.
What do you mean, "most commonly access functions"? Accessed by whom,
in what way? People "access" the functions via the extern declarations
in a matching header - not by looking up the implementation source. (If
they have to look up the source, you should put more effort into the
header file and its documentation.)
Richard Heathfield
2017-07-27 13:23:28 UTC
Permalink
Raw Message
<snip>
Post by David Brown
Post by bartc
Post by David Brown
If you think the order of the functions in a file does not matter, then
what is the problem with putting them in callee-caller order?
The problem is having to put them in callee-caller order.
It is no problem.
You have to put them in /some/ order - one function has to come before
the other. Why should it be such a problem to put the callee first?
In general, it isn't; but there are cases where it is very definitely a
problem, such as in a recursive descent parser, where you might have two
functions (e.g. factor() and term()) that are mutually recursive.
--
Richard Heathfield
Email: rjh at cpax dot org dot uk
"Usenet is a strange place" - dmr 29 July 1999
Sig line 4 vacant - apply within
David Brown
2017-07-27 13:52:46 UTC
Permalink
Raw Message
Post by Ben Bacarisse
<snip>
Post by David Brown
Post by bartc
Post by David Brown
If you think the order of the functions in a file does not matter, then
what is the problem with putting them in callee-caller order?
The problem is having to put them in callee-caller order.
It is no problem.
You have to put them in /some/ order - one function has to come before
the other. Why should it be such a problem to put the callee first?
In general, it isn't; but there are cases where it is very definitely a
problem, such as in a recursive descent parser, where you might have two
functions (e.g. factor() and term()) that are mutually recursive.
Yes, that has already been covered. In such a case, you will need a few
forward declarations of static functions (unless all the relevant
functions are exported and have declarations in a header file, of course).

But such cases are exceptions rather than rules (though they may be more
common in Bart's work).
bartc
2017-07-27 14:21:41 UTC
Permalink
Raw Message
Post by David Brown
Yes, that has already been covered. In such a case, you will need a few
forward declarations of static functions (unless all the relevant
functions are exported and have declarations in a header file, of course).
But such cases are exceptions rather than rules (though they may be more
common in Bart's work).
I very often have to generate C code. In that case, I /have/ to provide
a forward declaration list of all the functions in a file (including
exported ones), just to ensure no problem comes up where F calls G but G
is defined after F.

This is because the source language doesn't have any such restrictions,
and I'm not going to worry about function order to suit a possible C
target (most of the time it's not C), and I'm not going to do complex
analysis of call-trees to re-order the definitions.

That's not to say C should be changed to suit my translator, but to show
that such restrictions CAN BE a problem. Writing in C, I would make the
same decisions as to which function goes where (and usually I don't care).

Just as a test, I took the C output of that 92-function module (the
original isn't C), took out the forward declarations, and tried
compiling it. I now get 1150 lines of error and warning messages,
compared with 0 before.

(This module is a C parser, which is self-contained and can't
conveniently be split up as you suggested, which would also expose local
functions and variables. And the management of that new group of modules
would now be a bigger problem than providing forward declarations or
wasting time ensuring all functions are in call-tree order. For a start,
now I really DO need function declarations in a header to allow sharing!)

Forward function declarations in C are a nuisance, which if eliminated,
will not change anything for most people - they can program as before.
But for others it would be liberating.

(Out-of-order declarations of variables, structs, enums and typedefs are
a little different, as they can change how C code behaves.)
--
bartc
David Brown
2017-07-27 14:31:53 UTC
Permalink
Raw Message
Post by bartc
Post by David Brown
Yes, that has already been covered. In such a case, you will need a few
forward declarations of static functions (unless all the relevant
functions are exported and have declarations in a header file, of course).
But such cases are exceptions rather than rules (though they may be more
common in Bart's work).
I very often have to generate C code. In that case, I /have/ to provide
a forward declaration list of all the functions in a file (including
exported ones), just to ensure no problem comes up where F calls G but G
is defined after F.
When you are /generating/ C code automatically, very different rules
apply than when you are writing it manually. There is no problem
keeping a neat, consistent table of forward declarations - you just
generate it along with the code. Automatically generated C code is
generally very different stylistically from manual code - typically
functions are very flat, with gotos for loops, lots of temporary
variables called t1, t2, etc. It is much like the difference between
compiler-generated assembly code, and hand-written assembly.
Post by bartc
This is because the source language doesn't have any such restrictions,
and I'm not going to worry about function order to suit a possible C
target (most of the time it's not C), and I'm not going to do complex
analysis of call-trees to re-order the definitions.
That's not to say C should be changed to suit my translator, but to show
that such restrictions CAN BE a problem. Writing in C, I would make the
same decisions as to which function goes where (and usually I don't care).
Just as a test, I took the C output of that 92-function module (the
original isn't C), took out the forward declarations, and tried
compiling it. I now get 1150 lines of error and warning messages,
compared with 0 before.
(This module is a C parser, which is self-contained and can't
conveniently be split up as you suggested, which would also expose local
functions and variables. And the management of that new group of modules
would now be a bigger problem than providing forward declarations or
wasting time ensuring all functions are in call-tree order. For a start,
now I really DO need function declarations in a header to allow sharing!)
Having 92 functions in one file is an indication of a problem in the
structure of the code. It does not mean there definitely /is/ a problem
- but it is a warning sign, just like overly large functions would be.
Sometimes it really is the best way to structure the code.
Post by bartc
Forward function declarations in C are a nuisance, which if eliminated,
will not change anything for most people - they can program as before.
But for others it would be liberating.
(Out-of-order declarations of variables, structs, enums and typedefs are
a little different, as they can change how C code behaves.)
bartc
2017-07-27 14:44:25 UTC
Permalink
Raw Message
Post by David Brown
Post by bartc
Post by David Brown
Yes, that has already been covered. In such a case, you will need a few
forward declarations of static functions (unless all the relevant
functions are exported and have declarations in a header file, of course).
But such cases are exceptions rather than rules (though they may be more
common in Bart's work).
I very often have to generate C code. In that case, I /have/ to provide
a forward declaration list of all the functions in a file (including
exported ones), just to ensure no problem comes up where F calls G but G
is defined after F.
When you are /generating/ C code automatically, very different rules
apply than when you are writing it manually. There is no problem
keeping a neat, consistent table of forward declarations - you just
generate it along with the code. Automatically generated C code is
generally very different stylistically from manual code - typically
functions are very flat, with gotos for loops, lots of temporary
variables called t1, t2, etc. It is much like the difference between
compiler-generated assembly code, and hand-written assembly.
But that doesn't apply here. This is about function order.

Those functions are ordered in the source language in the same way they
would be if I wrote directly in C.

In C however, the ordering would generate compile errors (as I found on
that test) unless reordered and/or I generate forward declaration table.
Some functions will need a forward declaration because of mutual recursion.

The point is I don't have to worry about this stuff in my language, but
I would do if it was C. Maybe some other people (obviously not you
because your functions magically sort themselves out) would benefit from
that.


bartc
David Brown
2017-07-27 15:24:39 UTC
Permalink
Raw Message
Post by bartc
Post by David Brown
Post by bartc
Post by David Brown
Yes, that has already been covered. In such a case, you will need a few
forward declarations of static functions (unless all the relevant
functions are exported and have declarations in a header file, of course).
But such cases are exceptions rather than rules (though they may be more
common in Bart's work).
I very often have to generate C code. In that case, I /have/ to provide
a forward declaration list of all the functions in a file (including
exported ones), just to ensure no problem comes up where F calls G but G
is defined after F.
When you are /generating/ C code automatically, very different rules
apply than when you are writing it manually. There is no problem
keeping a neat, consistent table of forward declarations - you just
generate it along with the code. Automatically generated C code is
generally very different stylistically from manual code - typically
functions are very flat, with gotos for loops, lots of temporary
variables called t1, t2, etc. It is much like the difference between
compiler-generated assembly code, and hand-written assembly.
But that doesn't apply here. This is about function order.
Those functions are ordered in the source language in the same way they
would be if I wrote directly in C.
That makes sense - /when you are generating C code from another language/.
Post by bartc
In C however, the ordering would generate compile errors (as I found on
that test) unless reordered and/or I generate forward declaration table.
Some functions will need a forward declaration because of mutual recursion.
It is generated code - put in the table of forward declarations.

Can you not see the difference here? Automatically generated C is done
in a way that is convenient for the generator, and efficient when
compiled. It may or may not be designed to be human readable, but it
certainly will not be aimed at human modification and maintenance.
Post by bartc
The point is I don't have to worry about this stuff in my language, but
I would do if it was C. Maybe some other people (obviously not you
because your functions magically sort themselves out) would benefit from
that.
Well, as I have said before, I don't have to worry about it in C either
because I don't have a problem writing code in the order I want.
bartc
2017-07-27 15:41:20 UTC
Permalink
Raw Message
Post by David Brown
Post by bartc
But that doesn't apply here. This is about function order.
Those functions are ordered in the source language in the same way they
would be if I wrote directly in C.
That makes sense - /when you are generating C code from another language/.
I think you're missing the point again (maybe we /are/ going round in
circles!). The generating C bit is a red herring.

I write code in ANY language using functions A, B, C, D which allows
them to be in arbitrary order.

But if use C, or write the same code in C, or translate it to C, I have
to reorder them as D, B, A, C or declare some in advance because of the
call-structure.

There's a benefit in not having such a restriction that would be useful
to many writing C. But you want to deny that to people because you can't
see the point.
--
bartc
David Brown
2017-07-27 16:24:50 UTC
Permalink
Raw Message
Post by bartc
Post by David Brown
Post by bartc
But that doesn't apply here. This is about function order.
Those functions are ordered in the source language in the same way they
would be if I wrote directly in C.
That makes sense - /when you are generating C code from another language/.
I think you're missing the point again (maybe we /are/ going round in
circles!). The generating C bit is a red herring.
I write code in ANY language using functions A, B, C, D which allows
them to be in arbitrary order.
Some languages let you write the functions in any order, others require
forward declarations if you use a function before it is defined. C is
not unique in that respect. It is old-fashioned, perhaps - most modern
languages do not require function forward declarations. C++ does not
need it for methods within a class.
Post by bartc
But if use C, or write the same code in C, or translate it to C, I have
to reorder them as D, B, A, C or declare some in advance because of the
call-structure.
Yes. The same applies if you are translating to C++, Pascal, Fortran (I
think).
Post by bartc
There's a benefit in not having such a restriction that would be useful
to many writing C. But you want to deny that to people because you can't
see the point.
I am not arguing that it is not convenient to be able to write functions
in any order without forward declarations - nor that this would be a
difficult feature to add to C, nor a feature that some people would fine
helpful.

I am merely arguing that the need to declare or define functions before
use is not a problem, a "worry", or a particular effort - at least not
for me.

And I am also arguing that it /certainly/ should not be an issue for
automatically generated code.
Richard Heathfield
2017-07-27 14:33:33 UTC
Permalink
Raw Message
Post by David Brown
Post by Ben Bacarisse
<snip>
Post by David Brown
Post by bartc
Post by David Brown
If you think the order of the functions in a file does not matter, then
what is the problem with putting them in callee-caller order?
The problem is having to put them in callee-caller order.
It is no problem.
You have to put them in /some/ order - one function has to come before
the other. Why should it be such a problem to put the callee first?
In general, it isn't; but there are cases where it is very definitely a
problem, such as in a recursive descent parser, where you might have two
functions (e.g. factor() and term()) that are mutually recursive.
Yes, that has already been covered.
Fair enough. I wondered whether it might have been, but couldn't bring
myself to read the entire thread in sufficient detail. Sorry for the noise.
--
Richard Heathfield
Email: rjh at cpax dot org dot uk
"Usenet is a strange place" - dmr 29 July 1999
Sig line 4 vacant - apply within
David Brown
2017-07-27 15:26:19 UTC
Permalink
Raw Message
Post by Richard Heathfield
Post by David Brown
Post by Ben Bacarisse
<snip>
Post by David Brown
Post by bartc
Post by David Brown
If you think the order of the functions in a file does not matter, then
what is the problem with putting them in callee-caller order?
The problem is having to put them in callee-caller order.
It is no problem.
You have to put them in /some/ order - one function has to come before
the other. Why should it be such a problem to put the callee first?
In general, it isn't; but there are cases where it is very definitely a
problem, such as in a recursive descent parser, where you might have two
functions (e.g. factor() and term()) that are mutually recursive.
Yes, that has already been covered.
Fair enough. I wondered whether it might have been, but couldn't bring
myself to read the entire thread in sufficient detail. Sorry for the noise.
No problem.

And I think the thread is in danger of looking like mutual recursion as
the same points get argued again and again!
Jens Thoms Toerring
2017-07-27 10:34:15 UTC
Permalink
Raw Message
Post by jacobnavia
Post by Jens Thoms Toerring
All it would do is to make it IMPOSSIBLE for the compiler to
check function calls for correctness concerning the number of
arguments passed and their types.
Surely not.
1) The compiler sees a function call with no previous declaration.
2) It saves it in the "Undeclared" list
3) The compiler sees a function declaration.
4) It checks if it is in the undeclared list and
4) Verifies all arguments to the function in all previous calls.
You missed another point: the compiler also needs to store
somehow in what context the return value of a function was
used. E.g. is

x = 42 + foo(12.3, y, "something");

still a valid statement once you have finally figured out
what foo() returns? And if a function call is used as an
argument to another function you'd have to store in your
"Undeclared" list that the type of an argument is the re-
turn value of another function that's also in the "Unde-
clared" list and then unravel the resulting mess.
Post by jacobnavia
I can't see why this would be impossible to do.
Yeah, it possibly could be done for the current file the
compiler is working on - at quite a high cost since things
would become a lot more complex. But it won't work for
functions from other translation units. You'd either have
to relax the requirement to declare a function before use
only for functions from the same translation unit. That
sounds ridiculous (and not like what was proposd) - why
make a distinction between functions in different units?
Or a list of all calls and definitions of functions with
their argument and return types (as well as enough infor-
mation about the context the return value was used in)
would somehow have to be passed to the linker. That may
include static functions since their addresses may have
been used as function arguments in calls of functions from
a different translation unit (e.g. for use as callbacks).
And now the linker would have to do it all.

You'd need a linker that knows all about the C type system,
promotion rules etc. to do the job, which already is bad
enough. But what if some of the object files were generated
from e.g. FORTRAN sources and for which it doesn't get a
comparable list? Or also the FORTRAN compiler would have
to be modified to output such a list. And now the linker
would also have to know how the C and FORTRAN type systems
fit together. Oh, BTW, also all libraries would have to
come with such a list...
Regards, Jens
--
\ Jens Thoms Toerring ___ ***@toerring.de
\__________________________ http://toerring.de
Siri Cruise
2017-07-26 21:46:50 UTC
Permalink
Raw Message
Post by Jens Thoms Toerring
All it would do is to make it IMPOSSIBLE for the compiler to
check function calls for correctness concerning the number of
arguments passed and their types. The costs would be huge
Actually it easy and has been known since 1960 when Algol 60 was defined. You
build a parse tree and collect symbol table information. You then make a second
pass propagating symbol table information back and forth.

One Fortran compiler did it by tokenising the source with multiple keys. It
would do linear passes after sorting on different keys to propagate symbol table
information around the source representation.

Forward declarations were invented for Pascal to accomodate one pass compilers
that didn't build a complete parse tree and symbol table because only 65536
bytes were available. Nowadays a small memory address space is 2 billion bytes.
--
:-<> Siri Seal of Disavowal #000-001. Disavowed. Denied. Deleted. @
'I desire mercy, not sacrifice.' /|\
Free the Amos Yee one. This post / \
Yeah, too bad about your so-called life. Ha-ha. insults Islam. Mohammed
jacobnavia
2017-07-26 22:24:59 UTC
Permalink
Raw Message
Post by Siri Cruise
You then make a second
pass propagating symbol table information back and forth.
No, you can do it in one pass.

At each call of an unknown function, you note the argument types and
return value. You add that information to a "Undefined calls" list and
you generate code for a call with the actual types passed.

When you see a function definition or a prototype, you look into the
undefined list, and if you find that function, you verify all previous
calls and flag any errors.

If at the end of the compilation unit there are still undefined calls,
it is an error and compilation fails.

Still one pass.

jacob
bartc
2017-07-26 22:41:35 UTC
Permalink
Raw Message
Post by jacobnavia
Post by Siri Cruise
You then make a second
pass propagating symbol table information back and forth.
No, you can do it in one pass.
I doubt it. But it depends on what is included in that one pass.
Post by jacobnavia
At each call of an unknown function, you note the argument types and
return value. You add that information to a "Undefined calls" list and
you generate code for a call with the actual types passed.
The return type of a function which is not known until the end of the
file will determine how an expression is to be processed: promotions,
implicit conversions, as an operand of sizeof (which in turn will delay
reduction of constant expressions involving sizeof, or in knowing the
bounds of an array, or the value of a 'case' label).

Then there are the operands of the function call themselves: without
knowing what the types are meant to be, to can't finalise the expressions.
Post by jacobnavia
When you see a function definition or a prototype, you look into the
undefined list, and if you find that function, you verify all previous
calls and flag any errors.
And you might have to complete thousands of half-finished expressions.
Hopefully you won't have generated any code yet, as that would be a
nightmare sorting out.

It would be easier to just do the separate pass; it won't take long if
everything is already in memory.
--
bartc
jacobnavia
2017-07-26 23:09:07 UTC
Permalink
Raw Message
Post by bartc
The return type of a function which is not known until the end of the
file will determine how an expression is to be processed: promotions,
The function call reeturns a result that is assigned somewhere, I
suppose. The receiving type can determine the type of the return value
within some bounds. If code generation is impossible, you add either the
statement or the whole function to the undefined list.
Post by bartc
implicit conversions,
If the receiving type accepts conversions (not a structure) you can emit
NOPs to be patched later.

as an operand of sizeof

Mmm, function calls as operands of sizeof? Possible but not very common.
Actually, I have never seen it. Anyway, if that kind of code is written,
it is better to write the result type of the function. That would be an
error. Instead of sizeof( foo(0) ) you could write sizeof(int *) when
that function returns an int pointer.

(which in turn will delay
Post by bartc
reduction of constant expressions involving sizeof,
See above

or in knowing the
Post by bartc
bounds of an array,
I do not see where that would be the case

or the value of a 'case' label).

A case value is a constant expression, and function calls are not
allowed in C:

case foo() :

doesn't work at all.
bartc
2017-07-26 23:54:28 UTC
Permalink
Raw Message
Post by jacobnavia
Post by bartc
The return type of a function which is not known until the end of the
file will determine how an expression is to be processed: promotions,
The function call reeturns a result that is assigned somewhere, I
suppose. The receiving type can determine the type of the return value
within some bounds. If code generation is impossible, you add either the
statement or the whole function to the undefined list.
Post by bartc
implicit conversions,
If the receiving type accepts conversions (not a structure) you can emit
NOPs to be patched later.
This is where it gets untidy. You need to process an expression without
knowing the types of the operands, so have to create tentative code with
tentative conversions just in case.

Then there is:

void *p = abc;

abc is not in scope, so might be function defined later, or a variable
define later. Plus:

(*fn)();
(**fn)();

Extra derefs are ignored, but if an actual function pointer is involved
you need to have the right number of derefs, so tentative derefs might
be needed, to be fixed when the actual fn function is encountered, or an
actual fn function pointer.

All I'm saying is that it can get hairy enough, then it is easier to
just do the separate path. Then both can be streamlined.
Post by jacobnavia
A case value is a constant expression, and function calls are not
doesn't work at all.
I mean code like this:

int main(void) {
int a;

int A[sizeof(fn)*100];

switch (a){
case sizeof(fn()):
break;
}
}
--
Bartc
Keith Thompson
2017-07-27 00:09:06 UTC
Permalink
Raw Message
bartc <***@freeuk.com> writes:
[...]
Post by bartc
void *p = abc;
abc is not in scope, so might be function defined later, or a variable
define later.
It can't legally be a function. There is no implicit conversion from a
function pointer to void* (and an explicit declaration / cast has
undefined behavior).

[...]
--
Keith Thompson (The_Other_Keith) kst-***@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"
bartc
2017-07-27 00:22:07 UTC
Permalink
Raw Message
Post by Keith Thompson
[...]
Post by bartc
void *p = abc;
abc is not in scope, so might be function defined later, or a variable
define later.
It can't legally be a function. There is no implicit conversion from a
function pointer to void* (and an explicit declaration / cast has
undefined behavior).
This:

int fn(void){ return 0;}

....
void *p = fn;

Compiles without complaint on pelles c, lccwin, dmc, tcc and gcc (I'm
waiting for vs2017 for msvc to start up; sometimes small compilers are a
blessing).

For gcc, neither -Wall nor -Wextra would elicit even a warning, not with
any of -std=c90, c99 or c11.

Only -Wpedantic eventually gave me a warning. And the same option warned
me about //-comments!

So forgive me for not sharing your concern, not when no compiler takes
that seriously. And I've now tried MSVC, and that says nothing either.
--
bartc
Keith Thompson
2017-07-27 01:36:53 UTC
Permalink
Raw Message
Post by bartc
Post by Keith Thompson
[...]
Post by bartc
void *p = abc;
abc is not in scope, so might be function defined later, or a variable
define later.
It can't legally be a function. There is no implicit conversion from a
function pointer to void* (and an explicit declaration / cast has
undefined behavior).
int fn(void){ return 0;}
....
void *p = fn;
Compiles without complaint on pelles c, lccwin, dmc, tcc and gcc (I'm
waiting for vs2017 for msvc to start up; sometimes small compilers are a
blessing).
For gcc, neither -Wall nor -Wextra would elicit even a warning, not with
any of -std=c90, c99 or c11.
Then none of them are conforming implementations by default. That is
IMHO unfortunate, but it's hardly surprising, nor is it particularly
interesting.
Post by bartc
Only -Wpedantic eventually gave me a warning. And the same option warned
me about //-comments!
It only warns about //-comments if you specify -std=c89 or -std=c90 --
which is exactly what it should do.
Post by bartc
So forgive me for not sharing your concern, not when no compiler takes
that seriously. And I've now tried MSVC, and that says nothing either.
Forgive me for not caring about the lax behavior of most C compilers
(most of which take it perfectly seriously if you ask them to). I was
talking about the language. And since we're discussing a proposed
change to the language, in the unlikely event that it were actually
adopted I doubt that it would allow for the possibility that

void *p = abc;

might be an attempt to assign a function pointer to a void* object.
--
Keith Thompson (The_Other_Keith) kst-***@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"
bartc
2017-07-27 09:44:12 UTC
Permalink
Raw Message
Post by Keith Thompson
Then none of them are conforming implementations by default. That is
IMHO unfortunate, but it's hardly surprising, nor is it particularly
interesting.
It is interesting because it means as an error, it isn't taken
seriously. And for very typical hardware where a function pointer has
the same 32- or 64-bit representation as any other, it isn't, and it
will work perfectly well.
Post by Keith Thompson
Post by bartc
Only -Wpedantic eventually gave me a warning. And the same option warned
me about //-comments!
It only warns about //-comments if you specify -std=c89 or -std=c90 --
which is exactly what it should do.
Post by bartc
So forgive me for not sharing your concern, not when no compiler takes
that seriously. And I've now tried MSVC, and that says nothing either.
Forgive me for not caring about the lax behavior of most C compilers
(most of which take it perfectly seriously if you ask them to).
Most of them, really? That's a good cross-selection of recent C
compilers for Windows. (icc is missing but I understand it is not a free
product. And clang, but that will do what gcc tells it. But I've tried
both of these on gcc.godbolt.org using -xc -std=c11 -Wpedantic, and they
compile fine.)

I can see that gcc (and therefore clang) could be made to report this as
an actual error (if it's that serious, it must be an error), given
enough arm-twisting; how about all the others I've mentioned?

And is it worth the trouble just so we have a program that could still
still on some IBM mainframe where function pointers really are
different? (Better would be to change those odd implementations so
function pointers use the same internal representation as a void*, with
the pointer being a reference to the actual function 'pointer')
Post by Keith Thompson
I was
talking about the language.
Most people can only use the language practically via a compiler.
--
bartc
Keith Thompson
2017-07-27 15:50:21 UTC
Permalink
Raw Message
Post by bartc
Post by Keith Thompson
Then none of them are conforming implementations by default. That is
IMHO unfortunate, but it's hardly surprising, nor is it particularly
interesting.
It is interesting because it means as an error, it isn't taken
seriously. And for very typical hardware where a function pointer has
the same 32- or 64-bit representation as any other, it isn't, and it
will work perfectly well.
It means that some compilers don't take it seriously in their default,
non-conforming, mode. I take it seriously.
Post by bartc
Post by Keith Thompson
Post by bartc
Only -Wpedantic eventually gave me a warning. And the same option warned
me about //-comments!
It only warns about //-comments if you specify -std=c89 or -std=c90 --
which is exactly what it should do.
Post by bartc
So forgive me for not sharing your concern, not when no compiler takes
that seriously. And I've now tried MSVC, and that says nothing either.
Forgive me for not caring about the lax behavior of most C compilers
(most of which take it perfectly seriously if you ask them to).
Most of them, really? That's a good cross-selection of recent C
compilers for Windows. (icc is missing but I understand it is not a free
product. And clang, but that will do what gcc tells it. But I've tried
both of these on gcc.godbolt.org using -xc -std=c11 -Wpedantic, and they
compile fine.)
If you invoke the compiler using a tool *that hides warnings*, don't
complain that you don't see any warnings. More generally, if you refuse
to learn to use tools properly, don't complain when they don't work
properly. Try -pedantic-errors.
Post by bartc
I can see that gcc (and therefore clang) could be made to report this as
an actual error (if it's that serious, it must be an error), given
enough arm-twisting; how about all the others I've mentioned?
I don't know. I presume that most of them have a mode that attempts to
be conforming. As you should know by now, the standard doesn't require
any diagnostics not involving #error to be fatal; non-fatal warnings,
satisfy the standard's requirements.
Post by bartc
And is it worth the trouble just so we have a program that could still
still on some IBM mainframe where function pointers really are
different? (Better would be to change those odd implementations so
function pointers use the same internal representation as a void*, with
the pointer being a reference to the actual function 'pointer')
The language doesn't require function pointers and object pointers to
have the same representation, and it doesn't define the behavior of
conversions between them. Sometimes such conversions can be useful *in
non-portable code*.

If you think the language should be changed so such conversions are
well-defined for all implementations, feel free to make that argument.
Post by bartc
Post by Keith Thompson
I was
talking about the language.
Most people can only use the language practically via a compiler.
And of course you snipped the part of my article in which I said why
that's relevant.

If you're writing non-portable code that needs to convert between
function pointers and object pointers, you can invoke your compiler in a
non-conforming mode (or ignore warnings) and not worry about it. If
you're proposing a change to the language, you need to take into account
the way the language is currently defined.
--
Keith Thompson (The_Other_Keith) kst-***@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"
Robert Wessel
2017-07-27 16:48:12 UTC
Permalink
Raw Message
Post by bartc
Post by Keith Thompson
Then none of them are conforming implementations by default. That is
IMHO unfortunate, but it's hardly surprising, nor is it particularly
interesting.
It is interesting because it means as an error, it isn't taken
seriously. And for very typical hardware where a function pointer has
the same 32- or 64-bit representation as any other, it isn't, and it
will work perfectly well.
Post by Keith Thompson
Post by bartc
Only -Wpedantic eventually gave me a warning. And the same option warned
me about //-comments!
It only warns about //-comments if you specify -std=c89 or -std=c90 --
which is exactly what it should do.
Post by bartc
So forgive me for not sharing your concern, not when no compiler takes
that seriously. And I've now tried MSVC, and that says nothing either.
Forgive me for not caring about the lax behavior of most C compilers
(most of which take it perfectly seriously if you ask them to).
Most of them, really? That's a good cross-selection of recent C
compilers for Windows. (icc is missing but I understand it is not a free
product. And clang, but that will do what gcc tells it. But I've tried
both of these on gcc.godbolt.org using -xc -std=c11 -Wpedantic, and they
compile fine.)
I can see that gcc (and therefore clang) could be made to report this as
an actual error (if it's that serious, it must be an error), given
enough arm-twisting; how about all the others I've mentioned?
And is it worth the trouble just so we have a program that could still
still on some IBM mainframe where function pointers really are
different? (Better would be to change those odd implementations so
function pointers use the same internal representation as a void*, with
the pointer being a reference to the actual function 'pointer')
Post by Keith Thompson
I was
talking about the language.
Most people can only use the language practically via a compiler.
More than a few implementations have data and function pointers that
are not compatible. x86-16 compilers for example, often had different
size data and function pointers. Also many small implementations have
different sized function and data pointers or have such pointers
address completely disjoint (and differently managed) address spaces.

Now C *should* have some sort of generic function pointer, roughly
equivalent to what a void pointer does for data pointers. That it
doesn't means we often abuse void data pointers for the purpose.
Keith Thompson
2017-07-27 18:03:33 UTC
Permalink
Raw Message
Robert Wessel <***@yahoo.com> writes:
[...]
Post by Robert Wessel
Now C *should* have some sort of generic function pointer, roughly
equivalent to what a void pointer does for data pointers. That it
doesn't means we often abuse void data pointers for the purpose.
In effect, all function pointer types are "generic". You can convert a
value from one function pointer type to another and back again without
loss of information. You can always define:

typedef void (generic_function)(void);

or, if you prefer:

typedef void (*generic_function_pointer)(void);

The only thing missing relative to void* is that any conversions must be
explicit.

Non-portably, the POSIX dlsym() function returns a void* that may be
converted to a function pointer (if the name refers to a function).
POSIX does not, as I understand it, require compilers to meaningfully
support object-pointer-to-function-pointer conversions in general,
only for values returned by dlsym().

An implementation on which function pointers are bigger than void*,
for example, could have dlsym() return a pointer into a table
of function pointers, and make the conversion yield a value from
the table rather than doing the usual bitwise reinterpretation.
Any void* value not pointing to an entry in that table could not
meaningfully be converted to a function pointer.

POSIX could have defined separate functions for object and function
symbols, but it had to allow for existing practice.
--
Keith Thompson (The_Other_Keith) kst-***@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"
Siri Cruise
2017-07-27 01:44:12 UTC
Permalink
Raw Message
Post by bartc
int fn(void){ return 0;}
....
void *p = fn;
Compiles without complaint on pelles c, lccwin, dmc, tcc and gcc (I'm
waiting for vs2017 for msvc to start up; sometimes small compilers are a
blessing).
A function pointer needs a pointer to the code entry point (text segment) and
the static data (data and bss segments). The main program segment with static
libraries the linker can write the data addresses directly into the text
segment, but this is not possible with shared libraries.

Unix ensures a shared library data segment is always a fixed, known offset from
the text segment. This means the text segment can use text relative addresses
for data, thus can derive the data segment pointer from just the entry point
address; unix function pointer are thus just the same as (void*) and
interchangeable with any pointer coerceable to (void*).

However some systems do not (or did not) have the arrangement and must pass both
pointers so a function pointer is effectively struct{void *entrypt, *dataseg;}
which is not interchangeable with (void*).
--
:-<> Siri Seal of Disavowal #000-001. Disavowed. Denied. Deleted. @
'I desire mercy, not sacrifice.' /|\
Free the Amos Yee one. This post / \
Yeah, too bad about your so-called life. Ha-ha. insults Islam. Mohammed
Melzzzzz
2017-07-27 02:24:13 UTC
Permalink
Raw Message
Post by Siri Cruise
Post by bartc
int fn(void){ return 0;}
....
void *p = fn;
Compiles without complaint on pelles c, lccwin, dmc, tcc and gcc (I'm
waiting for vs2017 for msvc to start up; sometimes small compilers are a
blessing).
A function pointer needs a pointer to the code entry point (text segment) and
the static data (data and bss segments). The main program segment with static
libraries the linker can write the data addresses directly into the text
segment, but this is not possible with shared libraries.
Unix ensures a shared library data segment is always a fixed, known offset from
the text segment. This means the text segment can use text relative addresses
for data, thus can derive the data segment pointer from just the entry point
address; unix function pointer are thus just the same as (void*) and
interchangeable with any pointer coerceable to (void*).
However some systems do not (or did not) have the arrangement and must pass both
pointers so a function pointer is effectively struct{void *entrypt, *dataseg;}
which is not interchangeable with (void*).
That's simply false.
~/.../examples/assembler >>> cat nodata.asm
format elf64

public callme

section '.text' executable
callme:
mov rax,rdi
add rax,rsi
mov rcx,someval
add rax,[rcx]
ret
section '.nodata'
someval dq 42

~/.../examples/assembler >>> cat nodata.c
int (*callme)(int a,int b);
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
void *p=dlopen("nodata.so",RTLD_NOW);
callme = dlsym(p,"callme");
printf("%d\n",callme(0,0));
return 0;
}
~/.../examples/assembler >>> gcc -shared nodata.o -o nodata.so
~/.../examples/assembler >>> gcc nodata.c -ldl
~/.../examples/assembler >>> ./a.out
42
--
press any key to continue or any other to quit...
jacobnavia
2017-07-27 00:15:40 UTC
Permalink
Raw Message
Post by bartc
int main(void) {
int a;
int A[sizeof(fn)*100];
switch (a){
break;
}
}
That's a contrieved example... But the more I think about it, it looks
uglier and uglier...

:-)

int c = undeclared(42);

Many conversions are possible, depending on the result type of
"undeclared". The intermediate code would need to have some
"MaybeConvert" operations inserted. When actually generating code those
conversions would be eliminated, or not.

But it could be very unwidely.

Suppose a 3000 line GUI program that ends with

#include <windows.h>

UGGGGGG!!!!!
Rick C. Hodgin
2017-07-27 00:29:27 UTC
Permalink
Raw Message
Post by jacobnavia
Post by bartc
int main(void) {
int a;
int A[sizeof(fn)*100];
switch (a){
break;
}
}
That's a contrieved example... But the more I think about it, it looks
uglier and uglier...
:-)
int c = undeclared(42);
Many conversions are possible, depending on the result type of
"undeclared". The intermediate code would need to have some
"MaybeConvert" operations inserted. When actually generating code those
conversions would be eliminated, or not.
But it could be very unwidely.
Suppose a 3000 line GUI program that ends with
#include <windows.h>
UGGGGGG!!!!!
While such a thing would be theoretically possible, why code like
that? Just because an ability exists doesn't mean it should be
exploited in such a way.

My goals are primarily to resolve issues of interbred include
files which contain definitions referenced in other include files.

If you have a large enough system with enough structs or classes
that reference one other in other header files in a crossover manner,
it becomes increasingly difficult to ensure things are defined in
the right order so you can reference members rather than just ptrs.

I have spent a great deal of time in my Visual FreePro, Jr. project
creating files that are loaded ahead of others, so they can refer-
ence the members of things defined in other files. I have done
this because I have a file that handles lexing, but it also knows
about bitmaps, and the bitmap class handles bitmaps, but it also
knows about lexing components so they can be rendered properly
during debugging, etc.

This type of interbred library mandates something more than what
C/C++ offer in the way of forward declarations. With just that
one relaxation, I would be able to include my bitmaps.h file,
and my lexing.h file, and even though they reference components
in each other, neither would produce an error, and no special or
fancy programming workarounds would be required.

Thank you,
Rick C. Hodgin
Siri Cruise
2017-07-27 01:33:51 UTC
Permalink
Raw Message
Post by bartc
This is where it gets untidy. You need to process an expression without
Are you can just the gigabytes of real memory and multiple gigabytes of virtual
memory and fast processors with multiple cache levels, and just build a parse
tree and decorate it in successive traversals.
--
:-<> Siri Seal of Disavowal #000-001. Disavowed. Denied. Deleted. @
'I desire mercy, not sacrifice.' /|\
Free the Amos Yee one. This post / \
Yeah, too bad about your so-called life. Ha-ha. insults Islam. Mohammed
bartc
2017-07-26 22:05:10 UTC
Permalink
Raw Message
Post by Jens Thoms Toerring
What next? Remove the requirement to
declare a variable before its use?
Why not?

Here's an example from a C-class language that allows out-of-order
declarations:

-------------------------------
proc main =
a:=b+fn(c)
println a,g

int a=10,b=20,c=30
end

function fn(int x)int =
return x+1
end

int g=100
-------------------------------

Not only is the function fn declared after it's called, but the three
local variables in main() are declared at the end of the function! And
the global g can be declared at the end of the file too.

Names can be declared anywhere in their scope, but are treated are as
though they were declared at the top. (The output from this fragment is
51 100.)

Only user types need to be declared before use, as that affects parsing.
But it is possible to get around that, I think even in C, although C
does make it harder as there is more ambiguous syntax that requires
symbol table input.

Anyway this is never going to happen in C so no need to worry. I'm just
saying it is not an idiotic proposal; it can work.
--
bartc
Lynn McGuire
2017-07-26 20:55:38 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
And just to be clear, I'm not saying we should remove the forward-
declaration ability altogether, but just add a new option which
removes the requirement of having it.
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
Thank you,
Rick C. Hodgin
In C++'s strong typing system, how would you get rid of forward
declarations ? I see them as a necessary evil as we have about 22
classes forward declared in our main app.

Thanks,
Lynn
Rick C. Hodgin
2017-07-26 21:44:40 UTC
Permalink
Raw Message
Post by Lynn McGuire
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
And just to be clear, I'm not saying we should remove the forward-
declaration ability altogether, but just add a new option which
removes the requirement of having it.
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
In C++'s strong typing system, how would you get rid of forward
declarations ? I see them as a necessary evil as we have about 22
classes forward declared in our main app.
I don't propose getting rid of forward declarations, but enabling a
switch that would allow them to not be required when the switch is
in use.

For many projects, it would only be a matter of allowing the def-
inition for the thing you're referencing to be made somewhere in
source code rather than already in source code. And when parsing
on the first pass, you don't really do any parsing other than to
lex and identify things that are known, though you could also save
that for the next pass once you've loaded everything that may be
resolved later in code.

Thank you,
Rick C. Hodgin
Ben Bacarisse
2017-07-26 22:18:11 UTC
Permalink
Raw Message
"Rick C. Hodgin" <***@gmail.com> writes:
<snip>
Post by Rick C. Hodgin
I don't propose getting rid of forward declarations, but enabling a
switch that would allow them to not be required when the switch is
in use.
No change in the standard is required for that. And if the requirement
*were* removed from the standard it could not mandate a switch or any
other extra-linguistic mechanism for it. There might be a pre-defined
macro that code could test, or a pragma to turn the option on or off,
but nothing outside of C itself. The C standard has no jurisdiction
there.

<snip>
--
Ben.
David Brown
2017-07-27 07:29:05 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
Post by Lynn McGuire
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
And just to be clear, I'm not saying we should remove the forward-
declaration ability altogether, but just add a new option which
removes the requirement of having it.
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
In C++'s strong typing system, how would you get rid of forward
declarations ? I see them as a necessary evil as we have about 22
classes forward declared in our main app.
I don't propose getting rid of forward declarations, but enabling a
switch that would allow them to not be required when the switch is
in use.
The standards don't have "compiler switches" - that is something an
implementation has. There is nothing to stop an implementation having
such a switch if it wants, assuming it can be made to work properly.

This is, in fact, an essential requirement for getting such a change
into the C or C++ standards - it is expected that there be one or more
/major/ compiler implementations of a feature that have undergone
testing, evaluation and refinement. (By "major", I mean tools like gcc,
clang, MSVC - not something like Jacob's compiler, fine tool though it
may be. You need a large user base.)
Post by Rick C. Hodgin
For many projects, it would only be a matter of allowing the def-
inition for the thing you're referencing to be made somewhere in
source code rather than already in source code. And when parsing
on the first pass, you don't really do any parsing other than to
lex and identify things that are known, though you could also save
that for the next pass once you've loaded everything that may be
resolved later in code.
At first glance, I think it is likely that forward declarations could be
made redundant - though I certainly have not given it serious thought.
Basically, the compiler could first read through the file and whenever
it finds a function definition, class definition, struct definition,
etc., it could construct an appropriate forward declaration. Then it
could artificially insert these into the code and compile as normal.

It would be more of an inconvenience, I think, for other tools that
parse code in a simpler way, such as highlighting editors.

I suppose it all depends on your style of coding, but personally I
rarely need forward declarations - so they are no bother to me. The
only thing that annoys me about forward declarations is that some people
insist on putting a list of them at the start of their files, regardless
of their need - such lists can get inconsistent and are a maintenance
irritation.
bartc
2017-07-27 10:07:18 UTC
Permalink
Raw Message
Post by David Brown
At first glance, I think it is likely that forward declarations could be
made redundant - though I certainly have not given it serious thought.
Basically, the compiler could first read through the file and whenever
it finds a function definition, class definition, struct definition,
etc., it could construct an appropriate forward declaration. Then it
could artificially insert these into the code and compile as normal.
It wouldn't need to be that crude. That might be what a separate tool
would do to transform such C code into conventional C. (Actually,
exactly what I used once, which identified local and export functions
given stylised function definitions, and wrote out .cl and .cx header
files respectively.)
Post by David Brown
It would be more of an inconvenience, I think, for other tools that
parse code in a simpler way, such as highlighting editors.
This is not parsing, which stays the same.

If it wants to colour different kinds of identifiers in different ways,
then yes it might need to look ahead in the file, but it is not exactly
a trivial task anyway as it needs to resolve variables, typedefs,
macros, enum names, and labels as well as function names. (How does such
an editor manage with C++?)
Post by David Brown
I suppose it all depends on your style of coding, but personally I
rarely need forward declarations - so they are no bother to me.
I think everyone does unless they pay attention to the exact ordering of
functions in a file.

You're writing function F, and it calls G. Now you have to write G, but
you have to make sure it precedes F (so you can no longer read your code
top-down).

You now modify F and it calls a new function H, and H is inserted
between G and F.

Later, you modify G to call H too. But now that won't work, because G
won't see H. You have to now move H to just before G, or create (and
have to maintain) a forward declaration of H.

You can try inserting every new function at the top of the file, but
then, such a function may need to call an existing function, so that
won't work.

Or, you write function F which calls G which then calls F; where does
each go?

This stuff happens ALL THE TIME. I can't understand how you've never
come across it.
Post by David Brown
The
only thing that annoys me about forward declarations is that some people
insist on putting a list of them at the start of their files, regardless
of their need - such lists can get inconsistent and are a maintenance
irritation.
This is what the proposal will help eliminate. For local declarations
anyway. For exported, shared functions, it would need a much bigger
change to eliminate having to declare functions in headers.
--
bartc
David Brown
2017-07-27 12:01:45 UTC
Permalink
Raw Message
Post by bartc
Post by David Brown
At first glance, I think it is likely that forward declarations could be
made redundant - though I certainly have not given it serious thought.
Basically, the compiler could first read through the file and whenever
it finds a function definition, class definition, struct definition,
etc., it could construct an appropriate forward declaration. Then it
could artificially insert these into the code and compile as normal.
It wouldn't need to be that crude. That might be what a separate tool
would do to transform such C code into conventional C. (Actually,
exactly what I used once, which identified local and export functions
given stylised function definitions, and wrote out .cl and .cx header
files respectively.)
Certainly it can be done that way. There is the "cproto" program that
does a fair amount of that already, which I believe was designed for
automating the generation of function prototypes from old C90 and K&R C
for use in C99. A similar idea could be used here, with the benefit
that you don't need to change the C (or C++) compiler at all, nor the
standards.
Post by bartc
Post by David Brown
It would be more of an inconvenience, I think, for other tools that
parse code in a simpler way, such as highlighting editors.
This is not parsing, which stays the same.
If it wants to colour different kinds of identifiers in different ways,
then yes it might need to look ahead in the file, but it is not exactly
a trivial task anyway as it needs to resolve variables, typedefs,
macros, enum names, and labels as well as function names. (How does such
an editor manage with C++?)
No, I agree it is not a trivial task - so it is not helpful to make it
harder!
Post by bartc
Post by David Brown
I suppose it all depends on your style of coding, but personally I
rarely need forward declarations - so they are no bother to me.
I think everyone does unless they pay attention to the exact ordering of
functions in a file.
You're writing function F, and it calls G. Now you have to write G, but
you have to make sure it precedes F (so you can no longer read your code
top-down).
Very roughly, you can do "top down" coding (write F first, then write G)
or "bottom up" coding (write G first, then write F). If you put the
definition of G earlier in the source than the definition of F, then you
don't need forward declarations and your code /does/ read top-down if
you think of "bottom up" coding. And it means that if you want to find
the definition of a function you are using, you only need to look in one
direction - up the file.

That is not the way everyone likes to arrange their code in a file, but
it is a perfectly valid, logical, consistent and readable way to do it.

Modern editors and IDE (by "modern", I mean "this century") have no
problem with multiple windows on the same file, showing "table of
contents" or "outlines" of functions, jumping to definitions or
declarations of functions, etc.
Post by bartc
You now modify F and it calls a new function H, and H is inserted
between G and F.
Later, you modify G to call H too. But now that won't work, because G
won't see H. You have to now move H to just before G, or create (and
have to maintain) a forward declaration of H.
First, if you have a call graph where F calls G, and both F and G call
H, you probably have a poor structure in your code. The exception would
be when H is a more general utility-type function - and you would know
that when writing H, and thus place it earlier in the code in the first
place.

Second, cut and paste to move H is a few seconds work - it is less
effort than writing a forward declaration (even though that would not be
a significant effort either).
Post by bartc
You can try inserting every new function at the top of the file, but
then, such a function may need to call an existing function, so that
won't work.
Just write your functions in a sensible order. Sometimes that means
writing code that uses functions before they are defined, sometimes that
means moving about a bit in the file before writing a new function.

You are imagining problems - unless somehow I am unique in the way I
write code. It is rare that I ever have to move a function definition
around in code to get the order write, and rarer still that I ever need
to make forward declarations for my static functions (all non-static
functions have extern declarations in a matching header file).
Post by bartc
Or, you write function F which calls G which then calls F; where does
each go?
In the bin. Circular dependencies are almost always a terrible way to
structure your code. And for my type of work, recursion of any sort
should be met with extreme scepticism - stack space is limited,
reliability is paramount, and you have to be /very/ sure of /hard/ upper
limits for the recursion depths.

More generally, of course, this is an occasions where a forward function
declaration is necessary.
Post by bartc
This stuff happens ALL THE TIME. I can't understand how you've never
come across it.
No, it does not happen all the time - at least not when you have a plan
of the code you are writing.
Post by bartc
Post by David Brown
The
only thing that annoys me about forward declarations is that some people
insist on putting a list of them at the start of their files, regardless
of their need - such lists can get inconsistent and are a maintenance
irritation.
This is what the proposal will help eliminate. For local declarations
anyway. For exported, shared functions, it would need a much bigger
change to eliminate having to declare functions in headers.
I eliminate these lists of static forward declarations by not having
them. Problem solved.
bartc
2017-07-27 12:23:36 UTC
Permalink
Raw Message
Post by David Brown
Post by bartc
I think everyone does unless they pay attention to the exact ordering of
functions in a file.
You're writing function F, and it calls G. Now you have to write G, but
you have to make sure it precedes F (so you can no longer read your code
top-down).
Very roughly, you can do "top down" coding (write F first, then write G)
or "bottom up" coding (write G first, then write F). If you put the
definition of G earlier in the source than the definition of F, then you
don't need forward declarations and your code /does/ read top-down if
you think of "bottom up" coding. And it means that if you want to find
the definition of a function you are using, you only need to look in one
direction - up the file.
OK, so you have to worry about the ordering of functions in a file.
(I've just grabbed the first source file to hand, and counted 92 functions.)
Post by David Brown
First, if you have a call graph where F calls G, and both F and G call
H, you probably have a poor structure in your code. The exception would
be when H is a more general utility-type function - and you would know
that when writing H, and thus place it earlier in the code in the first
place.
Second, cut and paste to move H is a few seconds work - it is less
effort than writing a forward declaration (even though that would not be
a significant effort either).
So you have to think carefully about the ordering of functions in a
file. And at maintaining that order.
Post by David Brown
Post by bartc
You can try inserting every new function at the top of the file, but
then, such a function may need to call an existing function, so that
won't work.
Just write your functions in a sensible order.
So you have think carefully ... etc
Post by David Brown
Post by bartc
Or, you write function F which calls G which then calls F; where does
each go?
In the bin. Circular dependencies are almost always a terrible way to
structure your code. And for my type of work, recursion of any sort
should be met with extreme scepticism - stack space is limited,
With my type of work, there is a lot of recursion (not just in languages
but with any kind hierarchical data structure).
Post by David Brown
More generally, of course, this is an occasions where a forward function
declaration is necessary.
So now you have to have declarations.
Post by David Brown
Post by bartc
This stuff happens ALL THE TIME. I can't understand how you've never
come across it.
No, it does not happen all the time - at least not when you have a plan
of the code you are writing.
So you have to plan the layout, which means thinking carefully etc
Post by David Brown
I eliminate these lists of static forward declarations by not having
them. Problem solved.
Sorry, it isn't. You're 'solving' the problem by worrying about things
and doing a bunch of work that would otherwise not be necessary.

The proposal would do away with that leaving you free to concentrate on
other things.
--
bartc
David Brown
2017-07-27 13:07:14 UTC
Permalink
Raw Message
Post by bartc
Post by David Brown
Post by bartc
I think everyone does unless they pay attention to the exact ordering of
functions in a file.
You're writing function F, and it calls G. Now you have to write G, but
you have to make sure it precedes F (so you can no longer read your code
top-down).
Very roughly, you can do "top down" coding (write F first, then write G)
or "bottom up" coding (write G first, then write F). If you put the
definition of G earlier in the source than the definition of F, then you
don't need forward declarations and your code /does/ read top-down if
you think of "bottom up" coding. And it means that if you want to find
the definition of a function you are using, you only need to look in one
direction - up the file.
OK, so you have to worry about the ordering of functions in a file.
(I've just grabbed the first source file to hand, and counted 92 functions.)
I don't worry about the order of functions - I can write code in an
order that suits me without worrying. It is hard to tell, since I have
written my C code this way for decades, but I don't believe it is a
difficult habit to learn.

And 92 functions in one file is, IMHO, a good deal too many.
Post by bartc
Post by David Brown
First, if you have a call graph where F calls G, and both F and G call
H, you probably have a poor structure in your code. The exception would
be when H is a more general utility-type function - and you would know
that when writing H, and thus place it earlier in the code in the first
place.
Second, cut and paste to move H is a few seconds work - it is less
effort than writing a forward declaration (even though that would not be
a significant effort either).
So you have to think carefully about the ordering of functions in a
file. And at maintaining that order.
As I say, I don't have to think about it. And I don't have to "maintain
the order" - my functions do not wander about inside their files when I
am not looking. It is rare that I ever have to change the order of
functions within a file - usually that will only happen when doing some
major restructuring, in which case a bit of cut-and-paste is a
negligible detail in the work.
Post by bartc
Post by David Brown
Post by bartc
You can try inserting every new function at the top of the file, but
then, such a function may need to call an existing function, so that
won't work.
Just write your functions in a sensible order.
So you have think carefully ... etc
No - it does not take significant thought. It is really very, very simple.
Post by bartc
Post by David Brown
Post by bartc
Or, you write function F which calls G which then calls F; where does
each go?
In the bin. Circular dependencies are almost always a terrible way to
structure your code. And for my type of work, recursion of any sort
should be met with extreme scepticism - stack space is limited,
With my type of work, there is a lot of recursion (not just in languages
but with any kind hierarchical data structure).
Post by David Brown
More generally, of course, this is an occasions where a forward function
declaration is necessary.
So now you have to have declarations.
Yes. I have not suggested, at any time, that you don't sometimes need
forward declarations. I have only said that I very rarely need them
(excluding, of course, declarations in a header for exported functions).
If you are using recursion, you will need forward declarations on
occasion. (The same applies to types - for most types, you do not need
any kind of forward declarations. But for recursive structures, you do.)
Post by bartc
Post by David Brown
Post by bartc
This stuff happens ALL THE TIME. I can't understand how you've never
come across it.
No, it does not happen all the time - at least not when you have a plan
of the code you are writing.
So you have to plan the layout, which means thinking carefully etc
When I start writing code, I have a pretty good idea of the basic
structure of my code - the functions I will need, and which ones will
call others. Then I know the order they will go in the file. I may or
may not write them in that order - that will depend on the kind of code
I am writing, how much testing I will be doing underway, etc.
Post by bartc
Post by David Brown
I eliminate these lists of static forward declarations by not having
them. Problem solved.
Sorry, it isn't. You're 'solving' the problem by worrying about things
and doing a bunch of work that would otherwise not be necessary.
As I said, ordering the code as I do does not involve work or effort,
and certainly not worry. You are imagining a problem that does not
exist - simply because it means doing something in a way that is
different from your own habits.
Post by bartc
The proposal would do away with that leaving you free to concentrate on
other things.
It would change /nothing/ for me.
s***@casperkitty.com
2017-07-27 12:28:44 UTC
Permalink
Raw Message
Post by David Brown
In the bin. Circular dependencies are almost always a terrible way to
structure your code. And for my type of work, recursion of any sort
should be met with extreme scepticism - stack space is limited,
reliability is paramount, and you have to be /very/ sure of /hard/ upper
limits for the recursion depths.
I find it irksome that the Standard requires that compilers allows some
constructs to behave in arbitrary fashion if executed, but doesn't allow
conforming implementations to reject them outright. There is no depth of
function calls that is guaranteed not to blow the stack, so the requirement
that implementations "allow" any possible combination of call nesting serves
to preclude the possibility of having conforming implementation reject
programs whose stack usage can't be statically validated.

To be sure, only a fraction of useful programs could have their stack usage
statically validated, but in some fields a compiler that could guarantee
the safety of those programs would be more valuable than one which could
run other programs properly when given certain inputs but behave randomly
when given others.
David Brown
2017-07-27 13:28:40 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
Post by David Brown
In the bin. Circular dependencies are almost always a terrible way to
structure your code. And for my type of work, recursion of any sort
should be met with extreme scepticism - stack space is limited,
reliability is paramount, and you have to be /very/ sure of /hard/ upper
limits for the recursion depths.
I find it irksome that the Standard requires that compilers allows some
constructs to behave in arbitrary fashion if executed, but doesn't allow
conforming implementations to reject them outright.
I don't find that irksome - because it is not true. Compiler
implementations are free to give whatever diagnostics they want when
they see undefined behaviour (I assume that's what you mean by "behave
in an arbitrary fashion").

I find it irksome that even the best of compilers don't always warn
about or reject code that clearly has undefined behaviour, no matter
what warning and error options you use. But that is a quality of
implementation issue, not a standards issue.
Post by s***@casperkitty.com
There is no depth of
function calls that is guaranteed not to blow the stack, so the requirement
that implementations "allow" any possible combination of call nesting serves
to preclude the possibility of having conforming implementation reject
programs whose stack usage can't be statically validated.
It seems reasonable to me that a compiler should accept code unless it
is /sure/ that it will fail (such as by blowing the stack). It is
certainly possible that sometimes a compiler can see exactly how deep a
particular recursion will be, and that it also has information about
stack usage and maximum allowed stack depth - but that is going to be
very rare.
Post by s***@casperkitty.com
To be sure, only a fraction of useful programs could have their stack usage
statically validated, but in some fields a compiler that could guarantee
the safety of those programs would be more valuable than one which could
run other programs properly when given certain inputs but behave randomly
when given others.
Actually, quite a big proportion of small embedded systems can have
their stack usage analysed statically. But part of that is avoiding
recursion (or at least, being very, very careful with it), as well as
avoiding or limiting VLAs, and considering interrupt functions.
s***@casperkitty.com
2017-07-27 15:41:27 UTC
Permalink
Raw Message
Post by David Brown
Post by s***@casperkitty.com
I find it irksome that the Standard requires that compilers allows some
constructs to behave in arbitrary fashion if executed, but doesn't allow
conforming implementations to reject them outright.
I don't find that irksome - because it is not true. Compiler
implementations are free to give whatever diagnostics they want when
they see undefined behaviour (I assume that's what you mean by "behave
in an arbitrary fashion").
If a compiler cannot generate meaningful code for a function, I would think
that having it omit the function entirely would be more useful than having
it generate meaningless code. The Standard, however, would demand that if
a program could receive any inputs that would not result in the function
being called its existence shouldn't prevent the rest of the code from
executing (except that its existence would be allowed to push things over
a platform's limits and yield arbitrary behavior even when given defined
input).
Post by David Brown
I find it irksome that even the best of compilers don't always warn
about or reject code that clearly has undefined behaviour, no matter
what warning and error options you use. But that is a quality of
implementation issue, not a standards issue.
It could be a standards issue, if the Standard were to specify a class of
programs which implementations were not required to accept, but which could
only be accepted by implementations that could guarantee defined behavior
when no documented environmental requirements are violated (if an
implementation specifies that it needs AC120, tripping over the power cord
would violate an environmental requirement).
Post by David Brown
Post by s***@casperkitty.com
There is no depth of
function calls that is guaranteed not to blow the stack, so the requirement
that implementations "allow" any possible combination of call nesting serves
to preclude the possibility of having conforming implementation reject
programs whose stack usage can't be statically validated.
It seems reasonable to me that a compiler should accept code unless it
is /sure/ that it will fail (such as by blowing the stack). It is
certainly possible that sometimes a compiler can see exactly how deep a
particular recursion will be, and that it also has information about
stack usage and maximum allowed stack depth - but that is going to be
very rare.
Most programs that don't call outside code could be made statically
verifiable with the addition of a single compiler intrinsic which could
have a consistent meaning on all platforms: __stack_safe. Implementations
that define means of calling outside functions could likewise define means
of saying how much stack they require.

The __stack_safe intrinsic would yield 0 in cases where the compiler could
not statically verify that it could yield a non-zero value safely, and
would be allowed (but not required) to yield a non-zero value in cases
where a compiler could guarantee that it could; quality implementations
should try to yield non-zero values whenever practical subject to that
constraint. For a program to be statically verifiable, any recursive
calls must only be reachable *on branches where __stack_safe returns a
non-zero value*.

Example:

void print_tree_inorder(out_func_ptr *fn, node *p, error_code *stat)
{
if (!__STACK_SAFE)
{
*stat = STACK_OVERFLOW_ERROR_CODE;
return;
}
(*fn)(fn, "(");
print_tree_inorder(fn, p->left, stat);
(*fn)(fn, ")+(");
print_tree_inorder(fn, p->right, stat);
(*fn)(fn, ")");
}

No need for the function to artificially limit expression nesting depth
nor know how much stack space different calls will require. If functions
try to do as little as possible on the non-stack-safe path, the system
will be able to safely let implementations use most of the available stack.

Implementing such functionality should not be overly complicated; it could
probably be accommodated in ELF by a convention for using specially-named
symbols.

For each function, a compiler would need to supply the linker with a list
of functions that it will call if __stack_safe returns 0, along with the
maximum amount of stack space that might be reserved for each such call.
Each type of function pointer would have a dummy function name assigned
to it, and any calls to function pointers would be treated as calls to
that dummy function. The compiler would also supply a list of all
functions that may have their addresses stored into pointers; the linker
would treat calls to the dummy function as a call to all compatible
functions whose address is taken.

Given that information, a linker could build a non-cyclic call graph from
which it could compute the maximum amount of stack space that might be
needed to invoke any given function, and could create for each function a
symbol with that value. Linkers for the PIC and 8051 have supported such
functionality for decades; it's hardly rocket science.

A compiler targeting such a linker could implement __STACK_SAFE by having
it include for each such intrinsic a list of function calls on the "true"
branch, and along with its own stack usage at each call, and asking the
linker to generate a symbol for the maximum stack usage on that branch.
Generated code would compare the stack usage with that symbol and take
whichever branch was appropriate.

Note that there would be no need for the Standard to concern itself with
whether there was one stack or fifty, or how storage was allocated on it.
If a program doesn't contain any call cycles on branches where __STACK_SAFE
returns a non-zero value, it would be statically verifiable as safe, but
implementations could still return "1" in most cases where it would be safe
to do so.
Post by David Brown
Actually, quite a big proportion of small embedded systems can have
their stack usage analysed statically. But part of that is avoiding
recursion (or at least, being very, very careful with it), as well as
avoiding or limiting VLAs, and considering interrupt functions.
Static verification is useful, and need not be impractical. With the
addition of a single intrinsic it could even be made available to most
programs that would need to use recursion.

Although there are some cases where "best effort" processing may be
useful (it would not be possible to statically guarantee that something
will work, but it would anyway) there's no reason a good Standard should
not make it possible to perform most tasks in a way that would be
guaranteed to behave in some defined fashion, even if that fashion might
be to output "Sorry, this program can't be verified" and stop.
David Brown
2017-07-27 16:38:14 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
Post by David Brown
Post by s***@casperkitty.com
I find it irksome that the Standard requires that compilers allows some
constructs to behave in arbitrary fashion if executed, but doesn't allow
conforming implementations to reject them outright.
I don't find that irksome - because it is not true. Compiler
implementations are free to give whatever diagnostics they want when
they see undefined behaviour (I assume that's what you mean by "behave
in an arbitrary fashion").
If a compiler cannot generate meaningful code for a function, I would think
that having it omit the function entirely would be more useful than having
it generate meaningless code.
That is not unreasonable - though I would find it vastly /more/ useful
to get an error message in that case.

But that is not an issue with the /standards/, it is an issue with the
/implementation/.

If you write code:

int foo(int x) {
return x / 0;
}

Then there is /nothing/ in the standards preventing the compiler from
throwing a wobbly and rejecting your code. Doing so would be a good
implementation.

It is even allowed to print the message "you are too stupid to be a
programmer" and then format the disk. (That would be more questionable
implementation.)
Post by s***@casperkitty.com
The Standard, however, would demand that if
a program could receive any inputs that would not result in the function
being called its existence shouldn't prevent the rest of the code from
executing (except that its existence would be allowed to push things over
a platform's limits and yield arbitrary behavior even when given defined
input).
I can't parse that sentence - not without more coffee, or more whiskey.

If you are trying to say that the compiler is only allowed to reject
code if it can be sure there will be undefined behaviour, then that is
true. If you write:

int foo2(int x) {
return 1000 / x;
}

then the compiler should generate code for that (and it can ignore the
possibility of x being 0).
Post by s***@casperkitty.com
Post by David Brown
I find it irksome that even the best of compilers don't always warn
about or reject code that clearly has undefined behaviour, no matter
what warning and error options you use. But that is a quality of
implementation issue, not a standards issue.
It could be a standards issue, if the Standard were to specify a class of
programs which implementations were not required to accept, but which could
only be accepted by implementations that could guarantee defined behavior
when no documented environmental requirements are violated (if an
implementation specifies that it needs AC120, tripping over the power cord
would violate an environmental requirement).
The standards /could/ specify anything they like. But as I understand
it, you want the standards to specify that if the compiler can spot some
kinds of undefined behaviour, it should reject the code with an error.
I think that is a thoroughly bad idea. As it stands, the standard says
the compiler can do what it wants with the code - and the compiler can
choose to reject it. A weak compiler could not detect many cases of
undefined behaviour - a powerful compiler could do much more
sophisticated analysis and checking. It is not the standards place to
make demands about the quality and features of any particular C
implementation.
Post by s***@casperkitty.com
Post by David Brown
Post by s***@casperkitty.com
There is no depth of
function calls that is guaranteed not to blow the stack, so the requirement
that implementations "allow" any possible combination of call nesting serves
to preclude the possibility of having conforming implementation reject
programs whose stack usage can't be statically validated.
It seems reasonable to me that a compiler should accept code unless it
is /sure/ that it will fail (such as by blowing the stack). It is
certainly possible that sometimes a compiler can see exactly how deep a
particular recursion will be, and that it also has information about
stack usage and maximum allowed stack depth - but that is going to be
very rare.
Most programs that don't call outside code could be made statically
verifiable with the addition of a single compiler intrinsic which could
have a consistent meaning on all platforms: __stack_safe. Implementations
that define means of calling outside functions could likewise define means
of saying how much stack they require.
No, they could not. Not on their own - the compiler needs too much
additional information about the run-time environment.

It is possible to go some way towards it, however - I recommend you read
up about gcc's stack protection features.
Keith Thompson
2017-07-27 17:56:09 UTC
Permalink
Raw Message
David Brown <***@hesbynett.no> writes:
[...]
Post by David Brown
int foo(int x) {
return x / 0;
}
Then there is /nothing/ in the standards preventing the compiler from
throwing a wobbly and rejecting your code. Doing so would be a good
implementation.
[...]

I don't believe that's true. If the compiler can prove that foo
will always be called in any execution of the program, then the
program has undefined behavior, and terminating translation with
an error message is a valid response for a conforming implementation.

But this program:

static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); }

is, I believe, strictly conforming. A compiler can certainly warn
about the division, but (aside from the "one program" rule) it cannot
reject it. (FWIW, gcc generates no code for foo() at -O1 and above.)
--
Keith Thompson (The_Other_Keith) kst-***@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"
s***@casperkitty.com
2017-07-27 19:05:04 UTC
Permalink
Raw Message
Post by Keith Thompson
[...]
Post by David Brown
int foo(int x) {
return x / 0;
}
Then there is /nothing/ in the standards preventing the compiler from
throwing a wobbly and rejecting your code. Doing so would be a good
implementation.
[...]
I don't believe that's true. If the compiler can prove that foo
will always be called in any execution of the program, then the
program has undefined behavior, and terminating translation with
an error message is a valid response for a conforming implementation.
static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); }
is, I believe, strictly conforming. A compiler can certainly warn
about the division, but (aside from the "one program" rule) it cannot
reject it. (FWIW, gcc generates no code for foo() at -O1 and above.)
Given the code above, I would expect gcc to generate no code for foo()
even if it simply returned x, since it can statically see that it is
never called. The fact that it would perform a division by zero if it
were invoked is irrelevant.

A more interesting case would be:

static int foo(int x) { return 100/(x>>2); }
int main(int argc, char **argv)
{
if (argc==2)
return foo(argc);
else
return 0;
}

One could not determine whether "foo" could be invoked without UB without
actually performing some of the computations that would, syntactically, be
performed at runtime. The function appears to be reachable, and there are
some argument values for which its behavior would be defined. The fact
that all of the cases where it is reachable would yield UB should not
prevent proper code execution in cases where it isn't reachable.
David Kleinecke
2017-07-27 19:10:25 UTC
Permalink
Raw Message
Post by Keith Thompson
[...]
Post by David Brown
int foo(int x) {
return x / 0;
}
Then there is /nothing/ in the standards preventing the compiler from
throwing a wobbly and rejecting your code. Doing so would be a good
implementation.
[...]
I don't believe that's true. If the compiler can prove that foo
will always be called in any execution of the program, then the
program has undefined behavior, and terminating translation with
an error message is a valid response for a conforming implementation.
static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); }
is, I believe, strictly conforming. A compiler can certainly warn
about the division, but (aside from the "one program" rule) it cannot
reject it. (FWIW, gcc generates no code for foo() at -O1 and above.)
I dunno about later C's but the C89 standard offers an
interesting problem here. It says "If a return statement
without an expression is executed, and the value of the
function call is used by the caller, the behavior is
undefined." and adds that reaching the function-final }
is a return without value.

But how does the compiler know whether the function value
is used? Bad enough within a translation unit but worse
in a typed main because conforming is defined in terms of
compiling rather than execution.

I hope later C's have cleared this up.
Keith Thompson
2017-07-27 19:24:12 UTC
Permalink
Raw Message
[...]
Post by David Kleinecke
Post by Keith Thompson
static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); }
is, I believe, strictly conforming. A compiler can certainly warn
about the division, but (aside from the "one program" rule) it cannot
reject it. (FWIW, gcc generates no code for foo() at -O1 and above.)
I dunno about later C's but the C89 standard offers an
interesting problem here. It says "If a return statement
without an expression is executed, and the value of the
function call is used by the caller, the behavior is
undefined." and adds that reaching the function-final }
is a return without value.
Starting in C99, reaching the closing `}` of main() does an implicit
`return 0;`.

If you want a C90-compatible program that illustrates the same point:

static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); return 0; }
Post by David Kleinecke
But how does the compiler know whether the function value
is used?
Much of the point of undefined behavior is that the compiler doesn't
need to know.
Post by David Kleinecke
Bad enough within a translation unit but worse
in a typed main because conforming is defined in terms of
compiling rather than execution.
I hope later C's have cleared this up.
You know, you could always check.
--
Keith Thompson (The_Other_Keith) kst-***@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"
David Kleinecke
2017-07-27 22:26:59 UTC
Permalink
Raw Message
Post by Keith Thompson
[...]
Post by David Kleinecke
Post by Keith Thompson
static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); }
is, I believe, strictly conforming. A compiler can certainly warn
about the division, but (aside from the "one program" rule) it cannot
reject it. (FWIW, gcc generates no code for foo() at -O1 and above.)
I dunno about later C's but the C89 standard offers an
interesting problem here. It says "If a return statement
without an expression is executed, and the value of the
function call is used by the caller, the behavior is
undefined." and adds that reaching the function-final }
is a return without value.
Starting in C99, reaching the closing `}` of main() does an implicit
`return 0;`.
static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); return 0; }
Post by David Kleinecke
But how does the compiler know whether the function value
is used?
Much of the point of undefined behavior is that the compiler doesn't
need to know.
Post by David Kleinecke
Bad enough within a translation unit but worse
in a typed main because conforming is defined in terms of
compiling rather than execution.
I hope later C's have cleared this up.
You know, you could always check.
Then I couldn't bother comp.lang.c which is more fun. Lots
of the posters seem to agree with me.

C99 has the proper answer for the case of main.

But that doesn't seem to resolve the question for other
functions. Are you really going to force me to look at
the C99 standard?
Keith Thompson
2017-07-27 22:43:09 UTC
Permalink
Raw Message
[...]
Post by David Kleinecke
Post by Keith Thompson
Post by David Kleinecke
I hope later C's have cleared this up.
You know, you could always check.
Then I couldn't bother comp.lang.c which is more fun. Lots
of the posters seem to agree with me.
C99 has the proper answer for the case of main.
But that doesn't seem to resolve the question for other
functions. Are you really going to force me to look at
the C99 standard?
I can't force you to do anything, and I wouldn't if I could.

If you ask a question, I might answer it if nobody else beats me to it.
(No doubt there was an implied question in your statement "I hope later
C's have cleared this up.", but I'm not in the mood to go digging for
your meaning.)
--
Keith Thompson (The_Other_Keith) kst-***@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"
s***@casperkitty.com
2017-07-27 19:28:15 UTC
Permalink
Raw Message
Post by David Kleinecke
I dunno about later C's but the C89 standard offers an
interesting problem here. It says "If a return statement
without an expression is executed, and the value of the
function call is used by the caller, the behavior is
undefined." and adds that reaching the function-final }
is a return without value.
But how does the compiler know whether the function value
is used? Bad enough within a translation unit but worse
in a typed main because conforming is defined in terms of
compiling rather than execution.
I hope later C's have cleared this up.
Later versions of the C Standard make "main" special and specify that reaching
the end is equivalent to returning zero.

More generally, though, I would suggest that the proper way to read
that statement is to say that the Standard imposes no requirements
upon what happens if a caller tries to use the return value of a
function whose execution falls through without returning a value,
implying that implementations don't have to do anything special
when compiling a function like:

int foo(int x)
{
printf("Hey!");
if (x!=42) return bar(x);
}

to allow for the possibility that a caller which passes a value of zero
might use the return value. A compiler wouldn't be required to ensure
that the function returned zero in the x==42 case, for example.
David Brown
2017-07-28 09:32:41 UTC
Permalink
Raw Message
Post by Keith Thompson
[...]
Post by David Brown
int foo(int x) {
return x / 0;
}
Then there is /nothing/ in the standards preventing the compiler from
throwing a wobbly and rejecting your code. Doing so would be a good
implementation.
[...]
I don't believe that's true. If the compiler can prove that foo
will always be called in any execution of the program, then the
program has undefined behavior, and terminating translation with
an error message is a valid response for a conforming implementation.
static int foo(int x) { return x / 0; }
int main(void) { if (0) return foo(1); }
is, I believe, strictly conforming. A compiler can certainly warn
about the division, but (aside from the "one program" rule) it cannot
reject it. (FWIW, gcc generates no code for foo() at -O1 and above.)
Good point. I guess the compiler cannot reject "foo" above with an
error (in conforming modes) unless it can prove that the function will
/always/ be called somewhere in the program run.
s***@casperkitty.com
2017-07-27 20:32:12 UTC
Permalink
Raw Message
Post by David Brown
The standards /could/ specify anything they like. But as I understand
it, you want the standards to specify that if the compiler can spot some
kinds of undefined behaviour, it should reject the code with an error.
I think that is a thoroughly bad idea. As it stands, the standard says
the compiler can do what it wants with the code - and the compiler can
choose to reject it. A weak compiler could not detect many cases of
undefined behaviour - a powerful compiler could do much more
sophisticated analysis and checking. It is not the standards place to
make demands about the quality and features of any particular C
implementation.
The requirement would not be that implementations reject all programs that
have Undefined Behavior, but rather that implementations offer such a
guarantee for programs *from a certain class* (which I would call
"Selectively Conforming").

If a source file includes a directive specifying that it requires that
stack overflow actions have no consequence beyond forcing an abnormal
program termination at arbitrary time, implementations could process that
either by (1) statically validating stack usage, (2) generating run-time
code which would ensure that stack overflows would trap before causing
any other effects, or (3) rejecting the program if neither of the above
was deemed practical. If a directive specifies that option #2 would not
be acceptable, implementations that can't statically validate stack usage
would have to reject the program. That would limit the range of
implementations upon which a program would be useful, but would avoid the
need to have the "One program" rule allow arbitrary behavior.

An implementation that rejects anything other than one contrived and useless
program could be conforming, but one that behaves in unconstrained fashion
when given a Selectively Conforming program would be non-conforming.
s***@casperkitty.com
2017-07-27 21:56:55 UTC
Permalink
Raw Message
Post by David Brown
Post by s***@casperkitty.com
Most programs that don't call outside code could be made statically
verifiable with the addition of a single compiler intrinsic which could
have a consistent meaning on all platforms: __stack_safe. Implementations
that define means of calling outside functions could likewise define means
of saying how much stack they require.
No, they could not. Not on their own - the compiler needs too much
additional information about the run-time environment.
One would need either a linker that understood and could process
stack-usage info, the info, or else a build process that would use
a supplemental utility that would read all the stack-usage-related
symbols from all the object files and compute the additional symbols
used by the compiler. One would also need to have a run-time environment
that can determine what range of addresses the stack can safely occupy
in cases where that isn't resolvable at link time. One would also need
stack-usage info for the run-time library and any external functions
one wanted to call.

I can imagine scenarios where getting all of those things would be
impractical, but in most cases support for such a feature should be
practical.
Keith Thompson
2017-07-27 15:34:51 UTC
Permalink
Raw Message
***@casperkitty.com writes:
[...]
Post by s***@casperkitty.com
I find it irksome that the Standard requires that compilers allows some
constructs to behave in arbitrary fashion if executed, but doesn't allow
conforming implementations to reject them outright. There is no depth of
function calls that is guaranteed not to blow the stack, so the requirement
that implementations "allow" any possible combination of call nesting serves
to preclude the possibility of having conforming implementation reject
programs whose stack usage can't be statically validated.
A conforming compiler can certainly perform that kind of analysis and
warn about anything they like. You, as a programmer, can choose to
reject any source file that triggers such a warning. Or, if the
compiler supports it, you can invoke the compiler in a non-conforming
mode that makes such warnings fatal.
--
Keith Thompson (The_Other_Keith) kst-***@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"
Vir Campestris
2017-07-27 19:57:16 UTC
Permalink
Raw Message
On 27/07/2017 13:01, David Brown wrote:
<snip>
Post by David Brown
Second, cut and paste to move H is a few seconds work - it is less
effort than writing a forward declaration (even though that would not be
a significant effort either).
</snip>

You must have a very clever source control system.

I've never seen one that will distinguish "This lump of code has been
moved" from "This lump has been inserted, and that one deleted".

Which becomes really interesting in reviews, when you really need to
know whether it has been changed slightly when it moved.

Andy
Rick C. Hodgin
2017-07-27 20:18:10 UTC
Permalink
Raw Message
Post by Ben Bacarisse
<snip>
Post by David Brown
Second, cut and paste to move H is a few seconds work - it is less
effort than writing a forward declaration (even though that would not be
a significant effort either).
</snip>
You must have a very clever source control system.
I've never seen one that will distinguish "This lump of code has been
moved" from "This lump has been inserted, and that one deleted".
Which becomes really interesting in reviews, when you really need to
know whether it has been changed slightly when it moved.
Andy
From a comment Linus Torvalds made during this presentation on Git:

2007 - Google Tech Talk: Linus Torvalds on git


He said the ability exists within the git history to track movement
and editing of lines of code from their current location back to
their original location, even if they've moved from file to file.

He indicated it would be an expensive operation, and it would likely
involve writing some custom code to examine the evolution of blocks
over time, but he indicated it is possible with Git.

It does not appear to be something that's done during normal commit
cycles, however. IIRC, Git can identify that file x was renamed to
file y, even if it also has some changes within it.

Thank you,
Rick C. Hodgin
Paavo Helde
2017-07-27 20:53:39 UTC
Permalink
Raw Message
Post by Ben Bacarisse
<snip>
Post by David Brown
Second, cut and paste to move H is a few seconds work - it is less
effort than writing a forward declaration (even though that would not be
a significant effort either).
</snip>
You must have a very clever source control system.
I've never seen one that will distinguish "This lump of code has been
moved" from "This lump has been inserted, and that one deleted".
Which becomes really interesting in reviews, when you really need to
know whether it has been changed slightly when it moved.
If your ability to refactor code starts to become limited because of
source control and code review issues then you know you are in a big
trouble! Cart before horse and all that stuff...
David Brown
2017-07-28 07:20:48 UTC
Permalink
Raw Message
Post by Ben Bacarisse
<snip>
Post by David Brown
Second, cut and paste to move H is a few seconds work - it is less
effort than writing a forward declaration (even though that would not be
a significant effort either).
</snip>
You must have a very clever source control system.
I've never seen one that will distinguish "This lump of code has been
moved" from "This lump has been inserted, and that one deleted".
Which becomes really interesting in reviews, when you really need to
know whether it has been changed slightly when it moved.
Again, this is /not/ a problem - because it does not happen often.

And the period of the development process when you might be likely to be
moving bits around more, is the time when you are writing a lot of code
and making a lot of changes anyway.

I do accept, however, that moving code can make it harder to see changes
in the source when comparing versions - it is a valid argument against it.
Robert Wessel
2017-07-27 16:57:32 UTC
Permalink
Raw Message
On Thu, 27 Jul 2017 09:29:05 +0200, David Brown
Post by David Brown
Post by Rick C. Hodgin
Post by Lynn McGuire
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
And just to be clear, I'm not saying we should remove the forward-
declaration ability altogether, but just add a new option which
removes the requirement of having it.
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
In C++'s strong typing system, how would you get rid of forward
declarations ? I see them as a necessary evil as we have about 22
classes forward declared in our main app.
I don't propose getting rid of forward declarations, but enabling a
switch that would allow them to not be required when the switch is
in use.
The standards don't have "compiler switches" - that is something an
implementation has. There is nothing to stop an implementation having
such a switch if it wants, assuming it can be made to work properly.
This is, in fact, an essential requirement for getting such a change
into the C or C++ standards - it is expected that there be one or more
/major/ compiler implementations of a feature that have undergone
testing, evaluation and refinement. (By "major", I mean tools like gcc,
clang, MSVC - not something like Jacob's compiler, fine tool though it
may be. You need a large user base.)
Post by Rick C. Hodgin
For many projects, it would only be a matter of allowing the def-
inition for the thing you're referencing to be made somewhere in
source code rather than already in source code. And when parsing
on the first pass, you don't really do any parsing other than to
lex and identify things that are known, though you could also save
that for the next pass once you've loaded everything that may be
resolved later in code.
At first glance, I think it is likely that forward declarations could be
made redundant - though I certainly have not given it serious thought.
Basically, the compiler could first read through the file and whenever
it finds a function definition, class definition, struct definition,
etc., it could construct an appropriate forward declaration. Then it
could artificially insert these into the code and compile as normal.
It would be more of an inconvenience, I think, for other tools that
parse code in a simpler way, such as highlighting editors.
I suppose it all depends on your style of coding, but personally I
rarely need forward declarations - so they are no bother to me. The
only thing that annoys me about forward declarations is that some people
insist on putting a list of them at the start of their files, regardless
of their need - such lists can get inconsistent and are a maintenance
irritation.
While I have no real objection to making forward declarations option
in C, and it's certainly been done in many other languages, I've not
really thought about it enough to consider if there are any hidden
pitfalls (one of the big ones would have been the implicit definitions
which are no longer allowed). But you could almost certainly make
such a thing work for the vast majority of functions, and so long as
the language were defined in such a way that the compiler can diagnose
any remaining cases.

OTOH, while this might be a nice enhancement, it's a pretty small one
in terms of usability as far as I'm concerned. My list of things I
want improved in C (and C++) has a bunch of stuff I'd rate as having
much higher importance/significance than this change. So while I'd
not really object on any technical grounds, I'd object on the grounds
of this being an inappropriate priority. OTTH, the C committee really
doesn't pay much attention to my objections...
Tim Rentsch
2017-07-28 08:59:57 UTC
Permalink
Raw Message
Post by Robert Wessel
[changing C so forward declarations are not needed (for functions)]
While I have no real objection to making forward declarations option
in C, and it's certainly been done in many other languages, I've not
really thought about it enough to consider if there are any hidden
pitfalls (one of the big ones would have been the implicit definitions
which are no longer allowed). But you could almost certainly make
such a thing work for the vast majority of functions, and so long as
the language were defined in such a way that the compiler can diagnose
any remaining cases.
OTOH, while this might be a nice enhancement, it's a pretty small one
in terms of usability as far as I'm concerned.
Have you tried it? If you haven't tried it, what leads you to
think you have a good sense of what the impact would be?
Post by Robert Wessel
My list of things I want improved in C (and C++) has a bunch of
stuff I'd rate as having much higher importance/significance
than this change.
Can you say what some of those are (just for C, since I changed
the newsgroups line to be just comp.lang.c)?
Robert Wessel
2017-07-31 05:59:07 UTC
Permalink
Raw Message
On Fri, 28 Jul 2017 01:59:57 -0700, Tim Rentsch
Post by Tim Rentsch
Post by Robert Wessel
[changing C so forward declarations are not needed (for functions)]
While I have no real objection to making forward declarations option
in C, and it's certainly been done in many other languages, I've not
really thought about it enough to consider if there are any hidden
pitfalls (one of the big ones would have been the implicit definitions
which are no longer allowed). But you could almost certainly make
such a thing work for the vast majority of functions, and so long as
the language were defined in such a way that the compiler can diagnose
any remaining cases.
OTOH, while this might be a nice enhancement, it's a pretty small one
in terms of usability as far as I'm concerned.
Have you tried it? If you haven't tried it, what leads you to
think you have a good sense of what the impact would be?
Well, no, but I know what the need for function declarations or
definitions before use costs me now: namely a block of forward
declarations at the top of most of my programs. Given that I don't do
any significant amount work to maintain that as programs change, I
can't see how adding the option-forward-declarations feature would me
a major improvement (sure, every little bit helps, but it's just not a
problem where I spend much time working).
Post by Tim Rentsch
Post by Robert Wessel
My list of things I want improved in C (and C++) has a bunch of
stuff I'd rate as having much higher importance/significance
than this change.
Can you say what some of those are (just for C, since I changed
the newsgroups line to be just comp.lang.c)?
Not complete, and in no particular order, but:

Breaks/continues out of more than one loop/switch (I like the labeled
version, not the count version), a (required) 128 bit integer type, a
standard networking library for (at least) IP4/6, user defined
formatting extensions for printf, *better* formatting for printf (we
talked about one of those options a few weeks ago, maximum field
lengths), user defined input/output handling for streams, named
parameters (similar to named initializers), bit rotations, a standard
way to support array slices as parameters (a side effect of that would
be the ability to pass arrays generally *with* their size
information), coroutine support, support for defining bit-exact
structures, a standard string type that *isn't* null delimited
(probably necessitates making strings a first class type), 64-bit
versions of ftell and friends, some actually useful input function
(*scanf being essentially valueless, IMO), asynchronous I/O, some more
sophisticated external structures, a better scheme for implementing
generic code (whether that should be an expanded preprocessor or some
new mechanism more along the lines of C++ templates, I don't know).

And I'm perfectly willing to accept that not all platforms will be
able to support all of that. Optional features should be testable at
compile time, and in most cases should produce reasonable results if
possible (for example, asynchronous I/O might always execute
synchronously if AIO isn't supported by the implementation).

And yes, I'd rank pretty much everything in that list ahead of the
option-forward-declarations feature being discussed.

Things like the threading support have knocked some item off my list,
although in the case of threading, about 20 years too late.
Ben Bacarisse
2017-07-26 22:37:04 UTC
Permalink
Raw Message
"Rick C. Hodgin" <***@gmail.com> writes:
<snip>
Post by Rick C. Hodgin
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
Implement it in a widely used compiler (gcc and clang are two obvious
choices) and when/if it is seen to be useful and accepted a proposal
would stand a reasonable chance of success. And even if it were to
fail, the community would get the benefit (if any) from the feature.

<snip>
--
Ben.
Thiago Adams
2017-07-26 23:03:41 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
Is this for anything or just global scope functions? Is it for types?

What is the rule for:

void F()
{
T t;
typedef int T;
}
typedef double T;

or

typedef double T;

void F()
{
T t;
typedef int T;
}
Rick C. Hodgin
2017-07-27 00:09:29 UTC
Permalink
Raw Message
Post by Thiago Adams
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
Is this for anything or just global scope functions? Is it for types?
Everything.
Of course, these rules are arbitrarily chosen and could be altered
as needed or deemed fit.
Post by Thiago Adams
void F()
{
T t;
typedef int T;
}
typedef double T;
In the above example, the typedef within F() {..} would be in scope
first because it resides within the {..} and would, therefore, take
precedence.
Post by Thiago Adams
or
typedef double T;
void F()
{
T t;
typedef int T;
}
In this example, the similar the typedef within F() {..} is in scope
first, and
it would take precedence.

Thank you,
Rick C. Hodgin
Rick C. Hodgin
2017-07-27 00:11:33 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
Post by Thiago Adams
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
Is this for anything or just global scope functions? Is it for types?
Everything.
Of course, these rules are arbitrarily chosen and could be altered
as needed or deemed fit.
Post by Thiago Adams
void F()
{
T t;
typedef int T;
}
typedef double T;
In the above example, the typedef within F() {..} would be in scope
first because it resides within the {..} and would, therefore, take
precedence.
Post by Thiago Adams
or
typedef double T;
void F()
{
T t;
typedef int T;
}
In this example, the similar the typedef within F() {..} is in scope
first, and
it would take precedence.
I don't know what key combination I hit, but whatever it was sent
the message above sailing away before it was completed. :-)

It should read:

In this example, a similar situation as above would be in place, in
that there is a typedef definition within the {..} and it would also
take precedence.

Thank you,
Rick C. Hodgin
Thiago Adams
2017-07-27 00:59:33 UTC
Permalink
Raw Message
On Wednesday, July 26, 2017 at 9:09:37 PM UTC-3, Rick C. Hodgin wrote:
....
Post by Rick C. Hodgin
Post by Thiago Adams
or
typedef double T;
void F()
{
T t;
typedef int T;
}
In this example, the similar the typedef within F() {..} is in scope
first, and
it would take precedence.
but this is the opposite of the current behavior in c.
Rick C. Hodgin
2017-07-27 01:10:29 UTC
Permalink
Raw Message
Post by Thiago Adams
....
Post by Rick C. Hodgin
Post by Thiago Adams
or
typedef double T;
void F()
{
T t;
typedef int T;
}
In this example, the similar the typedef within F() {..} is in scope
first, and
it would take precedence.
but this is the opposite of the current behavior in c.
Yes. When the forward-declaration-requirement relaxation is enabled,
local scopes would take precedence over global scopes in such cases,
because it's looking outwardly scope-by-scope-by-scope...

Thank you,
Rick C. Hodgin
Thiago Adams
2017-07-27 01:56:55 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
Post by Thiago Adams
....
Post by Rick C. Hodgin
Post by Thiago Adams
or
typedef double T;
void F()
{
T t;
typedef int T;
}
In this example, the similar the typedef within F() {..} is in scope
first, and
it would take precedence.
but this is the opposite of the current behavior in c.
Yes. When the forward-declaration-requirement relaxation is enabled,
local scopes would take precedence over global scopes in such cases,
because it's looking outwardly scope-by-scope-by-scope...
In my C parser I build an AST. Then I can walk on it. Similar of second pass of compiler.

To do static analysis I need to rebuild the stack of declarations, or I need to search scopes like you said.
We can build an AST with all local scopes. Or rebuild scopes (stack of declarations) at the second pass.
David Brown
2017-07-27 11:53:37 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
Post by Thiago Adams
....
Post by Rick C. Hodgin
Post by Thiago Adams
or
typedef double T;
void F()
{
T t;
typedef int T;
}
In this example, the similar the typedef within F() {..} is in scope
first, and
it would take precedence.
but this is the opposite of the current behavior in c.
Yes. When the forward-declaration-requirement relaxation is enabled,
local scopes would take precedence over global scopes in such cases,
because it's looking outwardly scope-by-scope-by-scope...
You /definitely/ do not want the behaviour to change like this. Code
that has all its forward declarations in place should be treated
identically whether this "relaxation flag" is enabled or not.
Ian Collins
2017-07-28 08:48:17 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
Such a change would be largely irrelevant in C++ where most functions
are class members which are declared in class declarations. The reset
tend to be anonymous namespace functions which every coding standard
I've worked to live at the top of the compilation unit.
Post by Rick C. Hodgin
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
C++ name lookup rules are complicated enough without adding more
complexity...
--
Ian
Bo Persson
2017-07-28 09:43:34 UTC
Permalink
Raw Message
Post by Ian Collins
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
Such a change would be largely irrelevant in C++ where most functions
are class members which are declared in class declarations. The reset
tend to be anonymous namespace functions which every coding standard
I've worked to live at the top of the compilation unit.
And in C++ you can already abuse the rule than class members need not be
forward declared:

struct program
{
// all functions go here
};

The "member functions" can now freely call each other irrespective of
their declaration order.


Bo Persson
Rick C. Hodgin
2017-07-28 11:53:32 UTC
Permalink
Raw Message
Post by Bo Persson
Post by Ian Collins
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
Such a change would be largely irrelevant in C++ where most functions
are class members which are declared in class declarations. The reset
tend to be anonymous namespace functions which every coding standard
I've worked to live at the top of the compilation unit.
And in C++ you can already abuse the rule than class members need not be
struct program
{
// all functions go here
};
I've never known this. Do you know if something like this works
properly?

struct program
{
#include "file1.h"
#include "file2.h"
#include "file1.cpp"
#include "file2.cpp"
};

And in that way, then file1.h can reference member functions
defined in file2.h and given a body in file2.cpp?

If so, that might cure all of my woes. :-)
Post by Bo Persson
The "member functions" can now freely call each other irrespective of
their declaration order.
Awesome. Thank you.

Yours in software,
Rick C. Hodgin
David Brown
2017-07-28 14:14:47 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
Post by Bo Persson
Post by Ian Collins
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
Such a change would be largely irrelevant in C++ where most functions
are class members which are declared in class declarations. The
reset tend to be anonymous namespace functions which every coding
standard I've worked to live at the top of the compilation unit.
And in C++ you can already abuse the rule than class members need not
struct program
{
// all functions go here
};
I've never known this. Do you know if something like this works
properly?
struct program
{
#include "file1.h"
#include "file2.h"
#include "file1.cpp"
#include "file2.cpp"
};
That might work, I think, as long as there are no conflicts between the
files (like types, functions, etc., with the same name in both file1 and
file2). I believe that in theory, it is not valid to include standard
headers at any scope other than file scope - but in practice I expect it
would work.

Of course, main() would no longer be the start of the code - you'd need
to add something like:

int main() {
program prog;
return prog.main();
}

outside of the "struct program".

(If you have a lot of data, you might want to put "program prog" outside
of main so that it goes in the bss section rather than on the stack.)


Give it a shot, and see if it works for you.
Post by Rick C. Hodgin
And in that way, then file1.h can reference member functions
defined in file2.h and given a body in file2.cpp?
If so, that might cure all of my woes. :-)
Post by Bo Persson
The "member functions" can now freely call each other irrespective of
their declaration order.
Awesome. Thank you.
Yours in software,
Rick C. Hodgin
s***@casperkitty.com
2017-07-28 15:32:52 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
I've never known this. Do you know if something like this works
properly?
struct program
{
#include "file1.h"
#include "file2.h"
#include "file1.cpp"
#include "file2.cpp"
};
And in that way, then file1.h can reference member functions
defined in file2.h and given a body in file2.cpp?
Such a construct can be used to take C code which uses "global" variables
and make it so that one application can hold multiple instances of that
C code, each with its own set of global/static variables. That only works
for code which is written in the overlapping subset of C and C++, but it
can be useful if one wants to be able to simulate the behavior of a group
of interconnected devices running the same code. Code for the physical
devices would be built using an embedded C cross compiler, but if it's
designed properly it could also be fed (with suitable wrappers) into a
C++ compiler and used to simulate many devices. A function like

uint32_t packet_timeout;
uint32_t packet_retries;
void *sent_packet_dat;
uint32_t sent_packet_len;

void poll_timeout(void)
{
if (packet_timeout && !--packet_timeout)
{
if (packet_retries && --packet_retries)
{
transmit_packet(sent_packet_dat, sent_packet_len);
packet_retries--;
}
}
}

could within the embedded system access the indicated variables directly
without having to pass around a pointer to them, since at any point in
time the embedded system would only have one "sent packet". Feeding the
code into a C++ compiler would automatically turn that into code equivalent
to

struct my_context {
uint32_t packet_timeout;
uint32_t packet_retries;
void *sent_packet_dat;
uint32_t sent_packet_len;
... probably lots of other stuff
}

void poll_timeout(struct my_context *this)
{
if (this->packet_timeout && !--this->packet_timeout)
{
if (this->packet_retries && --this->packet_retries)
transmit_packet(this, this->sent_packet_dat, this->sent_packet_len);
}
}

Each simulated device would have its own my_context structure, capable of
holding an independent state.

Some people dislike the idea of writing code in "C/C++", but it can be a
useful "language" for purposes where code needs to run in both free-
standing and simulation environments. Even code is written to access
hardware registers directly can be supported if the pointers to the
registers are declared using suitable macros (for the embedded C compiler
they would be declared as simple pointers to actual hardware; for the
C++ simulation, they would be special objects such that *hw_ptr |= 0x1234;
would be processed as write_hw(&hw_ptr, read_ptr(&hw_ptr) | 0x1234), thus
allowing the simulation to behave like real hardware.
Rick C. Hodgin
2017-07-28 18:34:40 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
Post by Bo Persson
Post by Ian Collins
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
Such a change would be largely irrelevant in C++ where most functions
are class members which are declared in class declarations. The
reset tend to be anonymous namespace functions which every coding
standard I've worked to live at the top of the compilation unit.
And in C++ you can already abuse the rule than class members need not
struct program
{
// all functions go here
};
I've never known this. Do you know if something like this works
properly?
struct program
{
#include "file1.h"
#include "file2.h"
#include "file1.cpp"
#include "file2.cpp"
};
And in that way, then file1.h can reference member functions
defined in file2.h and given a body in file2.cpp?
If so, that might cure all of my woes. :-)
I was able to test this program in Visual Studio 2015 and it worked.
I was able to reference class xyz and its members before it was /
they were defined:

-----[ Begin ]-----

#include <stdlib.h>
#include <stdio.h>

struct STest
{
int a;

// Here's a reference to STest::xyz before it's defined
int STest_main(int argc, char* argv[])
{
STest::xyz* x = new STest::xyz();

x->m_int = 5;
x->printf("something");

return 0;
}

class xyz
{
public:
xyz() {};
~xyz() {};

void printf(char* text) { ::printf(text); }

public:
int m_int;
};

int b;
};

int main(int argc, char* argv[])
{
STest t;

t.STest_main(argc, argv);
return 0;
}

-----[ End ]-----

Thank you,
Rick C. Hodgin
Rick C. Hodgin
2017-07-28 18:40:31 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
-----[ Begin ]-----
#include <stdlib.h>
#include <stdio.h>
struct STest
{
int a;
// Here's a reference to STest::xyz before it's defined
int STest_main(int argc, char* argv[])
{
STest::xyz* x = new STest::xyz();
// In my haste at refactoring, I also forgot these other references,
// both of which work with a defined before, and b defined after.

a = 3;
b = 4;
Post by Rick C. Hodgin
x->m_int = 5;
x->printf("something");
return 0;
}
class xyz
{
xyz() {};
~xyz() {};
void printf(char* text) { ::printf(text); }
int m_int;
};
int b;
};
int main(int argc, char* argv[])
{
STest t;
t.STest_main(argc, argv);
return 0;
}
-----[ End ]-----
I think I may have found a reason to withdraw my earlier request
to have this non-forward declaration feature added to C/C++. It
already exists! (at least to some extent)

Thank you,
Rick C. Hodgin
Rick C. Hodgin
2017-07-28 11:48:47 UTC
Permalink
Raw Message
Post by Ian Collins
Post by Rick C. Hodgin
This is the year 2017. Computers are powerful, and advanced and have
algorithms for all kinds of compiler needs developed. The time of any
component of a modern compiler REQUIRING forward declarations is long
since past, save the needs of legacy software that was written for a
system with that expectation.
-----
What can be done to initiate a grass roots movement to remove the need
for having forward declarations in upcoming C and C++ Standards?
Such a change would be largely irrelevant in C++ where most functions
are class members which are declared in class declarations. The reset
tend to be anonymous namespace functions which every coding standard
I've worked to live at the top of the compilation unit.
What about the case where file1.h and file1.cpp exist, and they
possess their own functions and do their own thing autonomously.
And then file2.h and file2.cpp exist, and they do likewise. But
then later you decide you want to implement some features in file1
which reference file2 abilities, and features in file2 which also
reference file1 abilities.

If you only declare their references in the .h files and write the
bode code in the .cpp file then it works, but if you want something
like an accessor and want to code the entire function in the .h
file you cannot do it because in either the file1-to-file2, or the
file2-to-file1 reference, something will be undefined if you're
only using the file1.h and file2.h headers. You have to now create
something like a common.h header which includes those things file1
and file2 will share so you can include common.h ahead of both of
them, then all works.

And as you make your file1 and file2 work also with file3 and
file4 and fileN, then you begin to have this convoluted requirement
of making sure things are defined before their use, when they are
simply defined already a few lines further down. It seems to be
a legacy exercise in ridiculousness when computers today are more
than capable enough to perform the extra pass for us, saving us
the labor time and headaches of making things just right for the
compiler's needs. I prefer to place value ahead of the compiler,
and make the developer's needs paramount when the compiler is
capable of performing a task for us.

In my view, the existing system works well for pointer references,
but not for member references. At least that's been my experience.
I could be doing it wrong.

Thank you,
Rick C. Hodgin
Juha Nieminen
2017-07-31 10:41:44 UTC
Permalink
Raw Message
Post by Rick C. Hodgin
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
You can compile a compilation unit into an object file, a statically
linked file, or a dynamically linked file.

Even when compiling simply to an object file, the compiler might not,
at that point, have any access to the definition of a function in some
other source of object file. It's only at linking time that all the object
files come together.

How exactly is the compiler supposed to create the object file when it
can't see the function being called? This requires more than just a
function pointer to be set later. The parameters need to be put onto
the stack. The compiler can't know, without seeing the function
declaration, how it should put them there (eg. if you give an
int as parameter, but the function takes a double, the compiler
needs to do a conversion; it can't do that if it can't see that the
function is taking a double).

It becomes even more complicated with dynamically linked libraries.
Those may not be available at compile or even link time at all,
only at run time.
bartc
2017-07-31 10:56:51 UTC
Permalink
Raw Message
Post by Juha Nieminen
Post by Rick C. Hodgin
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
You can compile a compilation unit into an object file, a statically
linked file, or a dynamically linked file.
Even when compiling simply to an object file, the compiler might not,
at that point, have any access to the definition of a function in some
other source of object file. It's only at linking time that all the object
files come together.
How exactly is the compiler supposed to create the object file when it
can't see the function being called? This requires more than just a
function pointer to be set later. The parameters need to be put onto
the stack. The compiler can't know, without seeing the function
declaration, how it should put them there (eg. if you give an
int as parameter, but the function takes a double, the compiler
needs to do a conversion; it can't do that if it can't see that the
function is taking a double).
It becomes even more complicated with dynamically linked libraries.
Those may not be available at compile or even link time at all,
only at run time.
I think you're posting at cross-purposes.

As I understand it, the proposal is about relaxing requirements for
forward declarations of functions in the /same/ translation unit.

Not for eliminating declarations of functions in other modules (ie.
pretty much doing away with header files). As those wouldn't be
/forward/ declarations (more sideways ones).

So it means being able to write this:

-----------------------------
int main(void) { fn(10,20);}
void fn(float a,int b) {}
-----------------------------

instead of:

-----------------------------
void fn(float,int); // forward declaration of fn
int main(void) { fn(10,20);}
void fn(float a,int b) {}
-----------------------------

or:

-----------------------------
void fn(float a,int b) {} // re-ordering functions
int main(void) { fn(10,20);}
-----------------------------
--
bartc
Rick C. Hodgin
2017-07-31 12:33:47 UTC
Permalink
Raw Message
Post by Juha Nieminen
Post by Rick C. Hodgin
I think some kind of compiler switch which disables the forward-
declaration requirement, allowing an additional pass to be run on
source code to resolve unknown references would be desirable.
You can compile a compilation unit into an object file, a statically
linked file, or a dynamically linked file.
Even when compiling simply to an object file, the compiler might not,
at that point, have any access to the definition of a function in some
other source of object file. It's only at linking time that all the object
files come together.
How exactly is the compiler supposed to create the object file when it
can't see the function being called? This requires more than just a
function pointer to be set later. The parameters need to be put onto
the stack. The compiler can't know, without seeing the function
declaration, how it should put them there (eg. if you give an
int as parameter, but the function takes a double, the compiler
needs to do a conversion; it can't do that if it can't see that the
function is taking a double).
It becomes even more complicated with dynamically linked libraries.
Those may not be available at compile or even link time at all,
only at run time.
I do not advocate forward-declarations that are unknown across
object files, but only within a single compilation.

In many cases doing this will fail:

#include "file1.h"
#include "file2.h"

...because there are dependencies in file1.h which require the
information that's declared in file2.h. You must order them as:

#include "file2.h"
#include "file1.h"

But what can also happen is file1.h and file2.h both have some
dependencies on each other, so that if any members are referenced
the compilation will fail.

My goal in removing the requirement of having forward-declaration
is to resolve this issue so that something needs only be defined
somewhere in the compilation, and not before it's first referenced.

Many other languages have done away with the forward-declaration
requirement. It seems to be a proper thing for a compiler running
on modern hardware in 2017.

There's nothing to prevent a compiler from NOT implementing the
relaxation, and to maintain things as they are. In fact, I think
it would be essential for this relaxation to be only a feature
that can be turned on, and not a default feature.

Thank you,
Rick C. Hodgin

Loading...