Discussion:
If statement with initializer
(too old to reply)
j***@gmail.com
2017-05-19 08:17:21 UTC
Permalink
Raw Message
...
Consistency with other programming languages is a useful trait.
Consistency is a hobgoblin.
The quote refers to "A foolish consistency"; there's nothing
particularly foolish about this one.
...
Companies still trash resumes that claim C/C++ skills because they
assume that someone who really knows both won't group them that way.
The languages began diverging 30 years ago. They aren't really all
that close any more.
You can take almost any C program that has no syntax errors, constraint
violations, or undefined behavior, and, with only minor modifications,
convert it into code that has precisely the same defined behavior,
whether compiled as C code or as C++ code. That would not be true if the
languages had diverged as badly as you're suggesting.
Each version of the C standard since C90 has added features that have no
counterpart in C++, but later versions of C++ have often added some of
those same features. The ones that haven't yet been added to C++, are
relatively few, and not yet widely used. The biggest exceptions are
things like designated intializers and compound literals, but those are
merely convenience features for which there exists less convenient ways
to write the same thing that do have the same meaning in C and in C++.
Okay, you can write usable source in the mutual subset.

I'm not sure that some of my source that I think (heh) is valid C will
be compilable as C++ without some serious dinking in the details.

Going from C++ to C, of course, is a no-starter.

Twenty years ago, I put a lot of effort into learning how to write in the
subset. That effort derailed a couple of my private projects and at least
one project at work. It would not be going too far to say trying to use
that subset may have been one of the factors in my being asked to quit
that company.

--
Joel Rees

Trying to reinvent the industry all by myself:
http://defining-computers.blogspot.jp/
David Brown
2017-05-19 10:16:51 UTC
Permalink
Raw Message
Post by j***@gmail.com
...
Consistency with other programming languages is a useful trait.
Consistency is a hobgoblin.
The quote refers to "A foolish consistency"; there's nothing
particularly foolish about this one.
...
Companies still trash resumes that claim C/C++ skills because they
assume that someone who really knows both won't group them that way.
The languages began diverging 30 years ago. They aren't really all
that close any more.
You can take almost any C program that has no syntax errors, constraint
violations, or undefined behavior, and, with only minor modifications,
convert it into code that has precisely the same defined behavior,
whether compiled as C code or as C++ code. That would not be true if the
languages had diverged as badly as you're suggesting.
Each version of the C standard since C90 has added features that have no
counterpart in C++, but later versions of C++ have often added some of
those same features. The ones that haven't yet been added to C++, are
relatively few, and not yet widely used. The biggest exceptions are
things like designated intializers and compound literals, but those are
merely convenience features for which there exists less convenient ways
to write the same thing that do have the same meaning in C and in C++.
Okay, you can write usable source in the mutual subset.
I'm not sure that some of my source that I think (heh) is valid C will
be compilable as C++ without some serious dinking in the details.
Going from C++ to C, of course, is a no-starter.
Twenty years ago, I put a lot of effort into learning how to write in the
subset. That effort derailed a couple of my private projects and at least
one project at work. It would not be going too far to say trying to use
that subset may have been one of the factors in my being asked to quit
that company.
I really cannot comprehend why you think this is such a big deal. It is
/not/ hard to write C in a C++ compatible subset. It does not take "a
lot of effort" to learn. It might mean a change in your style of C, and
it means a few non-idiomatic points, but it is not difficult.

As you say, modern idiomatic C++ cannot be made C compatible in any
practical manner.
Richard Heathfield
2017-05-19 10:58:23 UTC
Permalink
Raw Message
On 19/05/17 11:16, David Brown wrote:
<snip>
Post by David Brown
I really cannot comprehend why you think this is such a big deal. It is
/not/ hard to write C in a C++ compatible subset.
It's even easier to write C in bloody-minded mode, such that it most
definitely is not a C++-compatible subset. For example, I habitually
write constructors like this:

T *new = malloc(sizeof *new);
if(new != NULL)
{
etc

which breaks in two different ways in a C++ compiler.

I didn't /start/ with the intention of making it C++-incompatible, of
course. I was just lucky.

The advantage of making C code C++-incompatible is that you don't have
to worry about obscure differences between the two languages. You can
just compile the C as C, wrap the header up in extern "C" for correct
inclusion into C++ modules, and link the C object into the project in
the usual manner.
--
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
j***@gmail.com
2017-05-20 10:20:04 UTC
Permalink
Raw Message
Post by David Brown
[...]
I really cannot comprehend why you think this is such a big deal. It is
/not/ hard to write C in a C++ compatible subset. It does not take "a
lot of effort" to learn. It might mean a change in your style of C, and
it means a few non-idiomatic points, but it is not difficult.
As you say, modern idiomatic C++ cannot be made C compatible in any
practical manner.
Twenty years ago, I was all for it.

I got burned on it. Badly. With idioms that I had understand were "going
to become part of the standard."

I suppose I should try compiling some of my current projects with g++.

Oops. There's one:

-----------------
makecelltype.c:362:51: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
makecelltype.c:366:52: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
-----------------

Here are the lines in the source:

--------------------------
...
char * headerName = "celltype.h";
char * sourceName;
char * headerMacro;
...
if ( ( sourceName = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
strncpy( sourceName, headerName, fileNameLength + 1 );
sourceName[ fileNameLength - 1 ] = 'c';
if ( ( headerMacro = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
...
--------------------------

What do you suggest I could do "better" here?

Wait. What is causing this code to error?

Well, that question aside, with cc -Wall, I get two warnings
about printf() formats on other lines that I won't try to
explain here why I have to ignore.

With c++ without options, I get the above two errors.

With c++ -Wall, I get, in addition to the print() warnings and
the above two errors, a long string of warnings about deprecated
conversions. Oh. And this beaut:

--------------------
makecelltype.c:441:41: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
--------------------

Source:

----------------------
else if ( ptrSize <= sizeof (ulongw_t) )
----------------------

Was there a reason I didn't use size_t there for ptrSize? I don't
remember. But the value of sizeof (ulongw_t) has to be known at
compile time here to be well within the safe range for that comparison.

I fought the compiler hard enough to get that file to compile cleanly
with standard C-compliant code, I'm not going back to work through those
warnings. That's not what I need to spend my time on.

This was for configuration. Using the configuration header generated
by gcc, compiling the main source, I get a lot of "too many initializers
for a union" errors that effectively kill the idea of using c++
to compile this.

The source code is here:

https://sourceforge.net/p/bif-c/code/HEAD/tree/trunk/

If you tell me that I should be writing those Forth words as C++
classes, what am I supposed to say to that kind of thinking?

If I wanted C++ classes I wouldn't be writing a Forth interpreter.

I'll be mildly interested in other suggestions.

--
Joel Rees

Trying to re-invent the industry all by myself:
http://defining-computers.blogspot.jp/
Ben Bacarisse
2017-05-20 14:18:29 UTC
Permalink
Raw Message
***@gmail.com writes:
<snip>
Post by j***@gmail.com
----------------------
else if ( ptrSize <= sizeof (ulongw_t) )
----------------------
Was there a reason I didn't use size_t there for ptrSize? I don't
remember. But the value of sizeof (ulongw_t) has to be known at
compile time here to be well within the safe range for that comparison.
The compiler can't remove the warning just because it knows sizeof
(ulongw_t) because the conversion goes the other way. The compiler is
telling you that a signed value (ptrSize) is going to be converted to
and unsigned one for the test so, for example, if ptrSize is -1 the
comparison will be false.

<snip>
--
Ben.
James Kuyper
2017-05-20 14:55:35 UTC
Permalink
Raw Message
Post by j***@gmail.com
Post by David Brown
[...]
I really cannot comprehend why you think this is such a big deal. It is
/not/ hard to write C in a C++ compatible subset. It does not take "a
lot of effort" to learn. It might mean a change in your style of C, and
it means a few non-idiomatic points, but it is not difficult.
As you say, modern idiomatic C++ cannot be made C compatible in any
practical manner.
Twenty years ago, I was all for it.
I got burned on it. Badly. With idioms that I had understand were "going
to become part of the standard."
I suppose I should try compiling some of my current projects with g++.
-----------------
makecelltype.c:362:51: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
makecelltype.c:366:52: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
-----------------
--------------------------
...
char * headerName = "celltype.h";
char * sourceName;
char * headerMacro;
...
if ( ( sourceName = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
strncpy( sourceName, headerName, fileNameLength + 1 );
sourceName[ fileNameLength - 1 ] = 'c';
if ( ( headerMacro = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
...
--------------------------
What do you suggest I could do "better" here?
It isn't a question of making the code better. This sub-thread traces
back to an assertion on your part that C and C++ had diverged so much
that they should be considered completely unrelated languages. I
responded by pointing out that you can modify just about any C program
so that it would compile with the same defined behavior in either
language. I was not recommending carrying out such a modification - I
was not saying that doing such a modification would make your code
better. I was merely pointing out that the fact that such a modification
was possible meant that C and C++ are in fact two very closely related
languages.

In this case, all you need to change is to explicitly cast the values
returned by malloc(). That makes the code worse marginally worse, from
the point of view of C programming, but it gives the code precisely the
same defined behavior when using either language to compile it.
Post by j***@gmail.com
Wait. What is causing this code to error?
C++ doesn't allow implicit conversion from void* to char*.
j***@gmail.com
2017-05-21 08:29:39 UTC
Permalink
Raw Message
Post by James Kuyper
Post by j***@gmail.com
Post by David Brown
[...]
I really cannot comprehend why you think this is such a big deal. It is
/not/ hard to write C in a C++ compatible subset. It does not take "a
lot of effort" to learn. It might mean a change in your style of C, and
it means a few non-idiomatic points, but it is not difficult.
As you say, modern idiomatic C++ cannot be made C compatible in any
practical manner.
Twenty years ago, I was all for it.
I got burned on it. Badly. With idioms that I had understand were "going
to become part of the standard."
I suppose I should try compiling some of my current projects with g++.
-----------------
makecelltype.c:362:51: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
makecelltype.c:366:52: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
-----------------
--------------------------
...
char * headerName = "celltype.h";
char * sourceName;
char * headerMacro;
...
if ( ( sourceName = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
strncpy( sourceName, headerName, fileNameLength + 1 );
sourceName[ fileNameLength - 1 ] = 'c';
if ( ( headerMacro = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
...
--------------------------
What do you suggest I could do "better" here?
It isn't a question of making the code better.
So I should fix what isn't broken?
Post by James Kuyper
This sub-thread traces
back to an assertion on your part that C and C++ had diverged so much
that they should be considered completely unrelated languages.
Did I say unrelated?

I'm pretty sure I said separate.
Post by James Kuyper
I
responded by pointing out that you can modify just about any C program
I was reading you to be rather absolute in asserting their
compatibility. How close to all do you mean when you say just about any?

And exactly how much modification?
Post by James Kuyper
so that it would compile with the same defined behavior in either
language. I was not recommending carrying out such a modification - I
was not saying that doing such a modification would make your code
better.
Okay, I misunderstood. Maybe it was because you seemed to be reporting
great success programming in your mutually compatible subset.

Still, ...
Post by James Kuyper
I was merely pointing out that the fact that such a modification
was possible meant that C and C++ are in fact two very closely related
languages.
Once closely related, but diverging.
Post by James Kuyper
In this case, all you need to change is to explicitly cast the values
returned by malloc().
Wait. malloc() doesn't return a char * in C++?
Post by James Kuyper
That makes the code worse marginally worse, from
the point of view of C programming, but it gives the code precisely the
same defined behavior when using either language to compile it.
Seriously? In both languages? Exact same semantics?
Post by James Kuyper
Post by j***@gmail.com
Wait. What is causing this code to error?
C++ doesn't allow implicit conversion from void* to char*.
If void * and char * will give the same effect, ...

Oh, never mind. In C++, there has to be a deeper root pointer type than
in C. Or something like that.

Still, the semantics have to be diverging.

Anyway, if you want to prove to me that my source code for that Forth
interpreter can be reasonably modified to compile okay as non-class
C++ source, I'll be interested in the results.

I don't really want to take the time myself, right now.

I'm thinking the union initializer errors are going to difficult to get
around, even if the configuration program ptrSize warnings issue can be
resolved for all the different CPUs and run-times I'm trying to keep the
code compatible with etc.

--
Joel Rees
Ian Collins
2017-05-21 08:39:28 UTC
Permalink
Raw Message
Post by j***@gmail.com
Post by James Kuyper
I was merely pointing out that the fact that such a modification
was possible meant that C and C++ are in fact two very closely related
languages.
Once closely related, but diverging.
Still very closely related.
Post by j***@gmail.com
Post by James Kuyper
In this case, all you need to change is to explicitly cast the values
returned by malloc().
Wait. malloc() doesn't return a char * in C++?
No and it doesn't in C either...
Post by j***@gmail.com
Post by James Kuyper
That makes the code worse marginally worse, from
the point of view of C programming, but it gives the code precisely the
same defined behavior when using either language to compile it.
Seriously? In both languages? Exact same semantics?
In this case, yes.
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
Wait. What is causing this code to error?
C++ doesn't allow implicit conversion from void* to char*.
If void * and char * will give the same effect, ...
Additional type safety come at the cost of requiring a cast.
Post by j***@gmail.com
Oh, never mind. In C++, there has to be a deeper root pointer type than
in C. Or something like that.
Still, the semantics have to be diverging.
Not since C99 added VLAs...
--
Ian
GOTHIER Nathan
2017-05-21 12:15:25 UTC
Permalink
Raw Message
On Sun, 21 May 2017 20:39:28 +1200
Post by Ian Collins
Additional type safety come at the cost of requiring a cast.
Is it safe to break old code with a casting error instead of a warning? How much
does it cost to rewrite these old codes?
James Kuyper
2017-05-21 19:29:13 UTC
Permalink
Raw Message
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
I suppose I should try compiling some of my current projects with g++.
-----------------
makecelltype.c:362:51: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
makecelltype.c:366:52: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
-----------------
--------------------------
...
char * headerName = "celltype.h";
char * sourceName;
char * headerMacro;
...
if ( ( sourceName = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
strncpy( sourceName, headerName, fileNameLength + 1 );
sourceName[ fileNameLength - 1 ] = 'c';
if ( ( headerMacro = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
...
--------------------------
What do you suggest I could do "better" here?
It isn't a question of making the code better.
So I should fix what isn't broken?
As I said, I'm not recommending that you make the change, I'm only
pointing out that the change can be made. If C and C++ had already
diverged as much as you say, that wouldn't be the case, and that's the
sole reason why I mentioned making such a conversion.
Post by j***@gmail.com
Post by James Kuyper
This sub-thread traces
back to an assertion on your part that C and C++ had diverged so much
that they should be considered completely unrelated languages.
Did I say unrelated?
I'm pretty sure I said separate.
Well, they're not very separated, either.
Post by j***@gmail.com
Post by James Kuyper
I
responded by pointing out that you can modify just about any C program
I was reading you to be rather absolute in asserting their
compatibility. How close to all do you mean when you say just about any?
Very close. I was primarily thinking in terms of code that does NOT
depend upon #ifdef __cplusplus - that doesn't really count as presenting
the same code to both the C and C++ compiler. Most C code can be
modified to have the same behavior without using #ifdef __cplusplus.
However, if you allow a small amount of #ifdef __cplusplus, essentially
all C code can be so modified.

Ironically, the cases that cause the most trouble involve relatively new
features of C that provide functionality similar to that provided by
different features of C++. For example, _Generic() was added in C2011,
and will probably never be added to C++, because C++ doesn't need it -
equivalent functionality can be achieved using overloaded functions.
Similar things can be said about the float and long-double versions of
the <math.h> functions, and about <tgmath.h>.
Post by j***@gmail.com
And exactly how much modification?
If you mean "exact" as in a precise percentage, I can't provide that. If
you look at Annex C of the C++ standard, you'll find a list of cases
that C and C++ handle differently. That section is only 9 pages long
(which is tiny, compared to the 701 pages n1570.pdf uses to describe C).
While there's a fair number of such differences, most of them are
obscure or easily dealt with, usually both. Compare that list with your
coding style, and you should get some idea how difficult it would be to
convert.
Post by j***@gmail.com
Post by James Kuyper
so that it would compile with the same defined behavior in either
language. I was not recommending carrying out such a modification - I
was not saying that doing such a modification would make your code
better.
Okay, I misunderstood. Maybe it was because you seemed to be reporting
great success programming in your mutually compatible subset.
I only said that conversion is possible, and not difficult. I never said
anything to suggest that I actually perform such conversions - I merely
am very well-informed about what would need to be converted - which
isn't much.
Post by j***@gmail.com
Still, ...
Post by James Kuyper
I was merely pointing out that the fact that such a modification
was possible meant that C and C++ are in fact two very closely related
languages.
Once closely related, but diverging.
Not very rapidly. C++ was originally intended to be an extension to C
that might be incorporated into the next version of the C standard. Even
when it became clear that it would have to become a separate language,
Stroustrup deliberately compromised on the design of C++ in order to
maintain compatibility with C. Both committees are firmly committed to a
policy of avoiding creation of gratuitous incompatibilities between the
two languages.
Post by j***@gmail.com
Post by James Kuyper
In this case, all you need to change is to explicitly cast the values
returned by malloc().
Wait. malloc() doesn't return a char * in C++?
Right - just like in C, malloc() returns void* in C++.
Post by j***@gmail.com
Post by James Kuyper
That makes the code worse marginally worse, from
the point of view of C programming, but it gives the code precisely the
same defined behavior when using either language to compile it.
Seriously? In both languages? Exact same semantics?
Yes. I'm curious - did you have any particular semantic differences you
were expecting to occur? If so, what were they?
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
Wait. What is causing this code to error?
C++ doesn't allow implicit conversion from void* to char*.
If void * and char * will give the same effect, ...
They won't - they're required to have the same representation, but they
don't have the same effect. Void* is meant to be a generic pointer type,
while char* is specifically for pointing at char. C allows for implicit
conversions from void* for convenience. C++ disallows it, because they
consider such conversions dangerous.
Post by j***@gmail.com
Oh, never mind. In C++, there has to be a deeper root pointer type than
in C. Or something like that.
Nope - void* is the closest thing either language has to a generic
pointer to object type. The key difference is that C++ depends heavily
upon operator overloading, which means that an implicit conversion from
void* to other pointer types would be excessively dangerous in C++.
Post by j***@gmail.com
Still, the semantics have to be diverging.
You might believe so, but reality hasn't been providing much support for
that conclusion. The biggest differences between the two languages have
been syntactic, not semantic. In most cases, syntax accepted as valid in
both languages has the same semantics in both languages.

I once went to considerable effort to write up a program demonstrating
every such case - there weren't many.

'a' has the type "int" in C, and the type "char" in C++.

Structs, unions, and enumerations can all have tags. In C, those tags
must always be accompanied by the 'struct', 'union' or 'enum' keywords.
Therefore, they have been given their own name space, separate from the
name space for ordinary identifiers. Thus, you can define a tag with the
same name as an object, and there's never any ambiguity. In C++, those
tags can be used without those keywords, so they can be confused with
ordinary identifiers. It is therefore not permitted to use the same
identifier as both a tag and as an object identifier in the same scope
in C++. You can demonstrate the difference in code with defined behavior
by creating situations where one declaration of an identifier hides a
declaration of that identifier with a different scope in C++, because
they are in the same name space, but fails to hide it in C, because they
are in different name spaces. I had to be careful when constructing my
demonstration, to avoid using the identifier in ways that would be a
syntax error or constraint violation for one of the two meanings. Real
life code would not be so careful; such code would almost certainly
cause a diagnostic for one reason or another.

In C, each struct or union type has it's own name space for members of
that type. In C++, they don't have their own name space, but each struct
or union has it's own scope. For many purposes, being in different name
spaces has a similar effect to being in a different scope, but it is
possible to write code with defined behavior in both languages where
that difference matters.
Post by j***@gmail.com
Anyway, if you want to prove to me that my source code for that Forth
interpreter can be reasonably modified to compile okay as non-class
C++ source, I'll be interested in the results.
Since I'm not recommending such a change, I don't see much point in
proving that it can be done. "reasonably" is also a potential debating
point - you might consider some of the required changes unreasonable.
j***@gmail.com
2017-05-23 06:38:50 UTC
Permalink
Raw Message
Post by James Kuyper
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
I suppose I should try compiling some of my current projects with g++.
-----------------
makecelltype.c:362:51: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
makecelltype.c:366:52: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
-----------------
--------------------------
...
char * headerName = "celltype.h";
char * sourceName;
char * headerMacro;
...
if ( ( sourceName = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
strncpy( sourceName, headerName, fileNameLength + 1 );
sourceName[ fileNameLength - 1 ] = 'c';
if ( ( headerMacro = malloc( fileNameLength + 1 ) ) == NULL )
return EXIT_FAILURE;
...
--------------------------
What do you suggest I could do "better" here?
It isn't a question of making the code better.
So I should fix what isn't broken?
As I said, I'm not recommending that you make the change, I'm only
pointing out that the change can be made. If C and C++ had already
diverged as much as you say, that wouldn't be the case, and that's the
sole reason why I mentioned making such a conversion.
Post by j***@gmail.com
Post by James Kuyper
This sub-thread traces
back to an assertion on your part that C and C++ had diverged so much
that they should be considered completely unrelated languages.
Did I say unrelated?
I'm pretty sure I said separate.
Well, they're not very separated, either.
Post by j***@gmail.com
Post by James Kuyper
I
responded by pointing out that you can modify just about any C program
I was reading you to be rather absolute in asserting their
compatibility. How close to all do you mean when you say just about any?
Very close. I was primarily thinking in terms of code that does NOT
depend upon #ifdef __cplusplus - that doesn't really count as presenting
the same code to both the C and C++ compiler. Most C code can be
modified to have the same behavior without using #ifdef __cplusplus.
However, if you allow a small amount of #ifdef __cplusplus, essentially
all C code can be so modified.
Ironically, the cases that cause the most trouble involve relatively new
features of C that provide functionality similar to that provided by
different features of C++. For example, _Generic() was added in C2011,
and will probably never be added to C++, because C++ doesn't need it -
equivalent functionality can be achieved using overloaded functions.
Similar things can be said about the float and long-double versions of
the <math.h> functions, and about <tgmath.h>.
Post by j***@gmail.com
And exactly how much modification?
If you mean "exact" as in a precise percentage, I can't provide that. If
you look at Annex C of the C++ standard, you'll find a list of cases
that C and C++ handle differently. That section is only 9 pages long
(which is tiny, compared to the 701 pages n1570.pdf uses to describe C).
While there's a fair number of such differences, most of them are
obscure or easily dealt with, usually both. Compare that list with your
coding style, and you should get some idea how difficult it would be to
convert.
Post by j***@gmail.com
Post by James Kuyper
so that it would compile with the same defined behavior in either
language. I was not recommending carrying out such a modification - I
was not saying that doing such a modification would make your code
better.
Okay, I misunderstood. Maybe it was because you seemed to be reporting
great success programming in your mutually compatible subset.
I only said that conversion is possible, and not difficult. I never said
anything to suggest that I actually perform such conversions - I merely
am very well-informed about what would need to be converted - which
isn't much.
Post by j***@gmail.com
Still, ...
Post by James Kuyper
I was merely pointing out that the fact that such a modification
was possible meant that C and C++ are in fact two very closely related
languages.
Once closely related, but diverging.
Not very rapidly. C++ was originally intended to be an extension to C
that might be incorporated into the next version of the C standard. Even
when it became clear that it would have to become a separate language,
Stroustrup deliberately compromised on the design of C++ in order to
maintain compatibility with C. Both committees are firmly committed to a
policy of avoiding creation of gratuitous incompatibilities between the
two languages.
Post by j***@gmail.com
Post by James Kuyper
In this case, all you need to change is to explicitly cast the values
returned by malloc().
Wait. malloc() doesn't return a char * in C++?
Right - just like in C, malloc() returns void* in C++.
My memory was wrong about this.

Mea culpa.

My memory that I deliberately chose a char pointer instead of a
void pointer in that code because I couldn't do what I wanted with
a void pointer might be suspect, as well.
Post by James Kuyper
Post by j***@gmail.com
Post by James Kuyper
That makes the code worse marginally worse, from
the point of view of C programming, but it gives the code precisely the
same defined behavior when using either language to compile it.
Seriously? In both languages? Exact same semantics?
Yes. I'm curious - did you have any particular semantic differences you
were expecting to occur? If so, what were they?
Well, what does adding one to a void pointer do?

My memory is that the compiler is supposed to tell me it doesn't
know how much 1 means relative to a void pointer, one way or another.

And I think that has something to do with why I didn't put the cast
in, but who knows whether the current expression would work okay with
a cast. I'd have to go back and compile it on at least three other
systems, one of which died, and I have no money to replace it. And it
may not be meaningful going forward.

C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
Post by James Kuyper
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
Wait. What is causing this code to error?
C++ doesn't allow implicit conversion from void* to char*.
If void * and char * will give the same effect, ...
They won't - they're required to have the same representation, but they
don't have the same effect. Void* is meant to be a generic pointer type,
while char* is specifically for pointing at char. C allows for implicit
conversions from void* for convenience. C++ disallows it, because they
consider such conversions dangerous.
Post by j***@gmail.com
Oh, never mind. In C++, there has to be a deeper root pointer type than
in C. Or something like that.
Nope - void* is the closest thing either language has to a generic
pointer to object type. The key difference is that C++ depends heavily
upon operator overloading, which means that an implicit conversion from
void* to other pointer types would be excessively dangerous in C++.
That's what I meant to say, even though I seem to have said it in a way
that you interpreted otherwise.

You see no problem with that. I see lots of problems.
Post by James Kuyper
Post by j***@gmail.com
Still, the semantics have to be diverging.
You might believe so, but reality hasn't been providing much support for
that conclusion. The biggest differences between the two languages have
been syntactic, not semantic. In most cases, syntax accepted as valid in
both languages has the same semantics in both languages.
I once went to considerable effort to write up a program demonstrating
every such case - there weren't many.
By your own admission, they were there.
Post by James Kuyper
'a' has the type "int" in C, and the type "char" in C++.
There's one.

C++ is one of the reasons we can't seem to shake loose of the
historical misnaming of the byte type.
Post by James Kuyper
Structs, unions, and enumerations can all have tags.
Here we go.
Post by James Kuyper
In C, those tags
must always be accompanied by the 'struct', 'union' or 'enum' keywords.
Therefore, they have been given their own name space, separate from the
name space for ordinary identifiers. Thus, you can define a tag with the
same name as an object, and there's never any ambiguity. In C++, those
tags can be used without those keywords, so they can be confused with
ordinary identifiers. It is therefore not permitted to use the same
identifier as both a tag and as an object identifier in the same scope
in C++. You can demonstrate the difference in code with defined behavior
by creating situations where one declaration of an identifier hides a
declaration of that identifier with a different scope in C++, because
they are in the same name space, but fails to hide it in C, because they
are in different name spaces. I had to be careful when constructing my
demonstration, to avoid using the identifier in ways that would be a
syntax error or constraint violation for one of the two meanings. Real
life code would not be so careful; such code would almost certainly
cause a diagnostic for one reason or another.
This is probably at the root of the other error messages that compiling
as C++ produced.
Post by James Kuyper
In C, each struct or union type has it's own name space for members of
that type. In C++, they don't have their own name space, but each struct
or union has it's own scope. For many purposes, being in different name
spaces has a similar effect to being in a different scope, but it is
possible to write code with defined behavior in both languages where
that difference matters.
I might be wrong, but I don't want to try and find out how much
re-writing it would take to clear the union initialization errors I
hit.
Post by James Kuyper
Post by j***@gmail.com
Anyway, if you want to prove to me that my source code for that Forth
interpreter can be reasonably modified to compile okay as non-class
C++ source, I'll be interested in the results.
Since I'm not recommending such a change, I don't see much point in
proving that it can be done. "reasonably" is also a potential debating
point - you might consider some of the required changes unreasonable.
If I can't initialize those unions, I might as well throw that whole
project away.

It's only sixteen thousand lines of comment and code. Why not?

The languages are different. The fact that there is a subset usable
in some contexts does not mean they are not different.

Quietly different can be the scariest kind of different, too.

--
Joel Rees

Delusions of being a novelist:
http://reiisi.blogspot.com/p/novels-i-am-writing.html
Keith Thompson
2017-05-23 07:16:28 UTC
Permalink
Raw Message
[...]
Post by j***@gmail.com
Well, what does adding one to a void pointer do?
My memory is that the compiler is supposed to tell me it doesn't
know how much 1 means relative to a void pointer, one way or another.
Right, arithmetic on void* is not supported; it's a constraint
violation, requiring a diagnostic. (gcc supports arithmetic on void* as
an extension, treating it like char*. An annoying side effect is that
it treats sizeof (void) as 1.)

[...]
Post by j***@gmail.com
Post by James Kuyper
Since I'm not recommending such a change, I don't see much point in
proving that it can be done. "reasonably" is also a potential debating
point - you might consider some of the required changes unreasonable.
If I can't initialize those unions, I might as well throw that whole
project away.
Can you show a small example of the union initialization that you find
troubling?

[...]
--
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"
Ian Collins
2017-05-23 07:26:46 UTC
Permalink
Raw Message
Post by j***@gmail.com
Post by James Kuyper
Yes. I'm curious - did you have any particular semantic differences you
were expecting to occur? If so, what were they?
Well, what does adding one to a void pointer do?
Give a compilation error in both languages.
Post by j***@gmail.com
My memory is that the compiler is supposed to tell me it doesn't
know how much 1 means relative to a void pointer, one way or another.
And I think that has something to do with why I didn't put the cast
in, but who knows whether the current expression would work okay with
a cast. I'd have to go back and compile it on at least three other
systems, one of which died, and I have no money to replace it. And it
may not be meaningful going forward.
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
C is a very stable language. C changes at its own pace, it it chooses
to pinch odds and ends from C++, it does.
Post by j***@gmail.com
Post by James Kuyper
Nope - void* is the closest thing either language has to a generic
pointer to object type. The key difference is that C++ depends heavily
upon operator overloading, which means that an implicit conversion from
void* to other pointer types would be excessively dangerous in C++.
That's what I meant to say, even though I seem to have said it in a way
that you interpreted otherwise.
You see no problem with that. I see lots of problems.
Such as? You have been very vague thus far.

Maybe your opinions stem for a lack of C as well as C++ knowledge?
Post by j***@gmail.com
Post by James Kuyper
You might believe so, but reality hasn't been providing much support for
that conclusion. The biggest differences between the two languages have
been syntactic, not semantic. In most cases, syntax accepted as valid in
both languages has the same semantics in both languages.
I once went to considerable effort to write up a program demonstrating
every such case - there weren't many.
By your own admission, they were there.
Post by James Kuyper
'a' has the type "int" in C, and the type "char" in C++.
There's one.
C++ is one of the reasons we can't seem to shake loose of the
historical misnaming of the byte type.
Is it?
Post by j***@gmail.com
Post by James Kuyper
Structs, unions, and enumerations can all have tags.
Here we go.
Go where?
Post by j***@gmail.com
Post by James Kuyper
In C, those tags
must always be accompanied by the 'struct', 'union' or 'enum' keywords.
Therefore, they have been given their own name space, separate from the
name space for ordinary identifiers. Thus, you can define a tag with the
same name as an object, and there's never any ambiguity. In C++, those
tags can be used without those keywords, so they can be confused with
ordinary identifiers. It is therefore not permitted to use the same
identifier as both a tag and as an object identifier in the same scope
in C++. You can demonstrate the difference in code with defined behavior
by creating situations where one declaration of an identifier hides a
declaration of that identifier with a different scope in C++, because
they are in the same name space, but fails to hide it in C, because they
are in different name spaces. I had to be careful when constructing my
demonstration, to avoid using the identifier in ways that would be a
syntax error or constraint violation for one of the two meanings. Real
life code would not be so careful; such code would almost certainly
cause a diagnostic for one reason or another.
This is probably at the root of the other error messages that compiling
as C++ produced.
Note the errors are in syntax, not semantics.
--
Ian
j***@gmail.com
2017-05-23 23:25:18 UTC
Permalink
Raw Message
Post by Ian Collins
Post by j***@gmail.com
[...]
This is probably at the root of the other error messages that compiling
as C++ produced.
Note the errors are in syntax, not semantics.
You got the source I referenced and looked at it, then?

If so, could you be more specific?

--
Joel Rees

randomly ranting:
http://reiisi.blogspot.com
Ian Collins
2017-05-24 08:38:14 UTC
Permalink
Raw Message
Post by j***@gmail.com
Post by Ian Collins
Post by j***@gmail.com
[...]
This is probably at the root of the other error messages that compiling
as C++ produced.
Note the errors are in syntax, not semantics.
You got the source I referenced and looked at it, then?
No, I was referring to the paragraph you snipped.
--
Ian
David Brown
2017-05-23 07:33:02 UTC
Permalink
Raw Message
Post by j***@gmail.com
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
C is a very stable language. I don't know of any other languages that
can compare in popularity and age, and are close to the stability of C.
The prime reason for C++ being split off as a separate language (it was
originally planned as a future for C) was to let C remain stable.

And can you give examples of the "churn" in C due to these "unreasonable
efforts" to keep compatibility with C++ ? You should specifically
exclude features that are of clear benefit to the C language (in the
eyes of many) from your list.
Post by j***@gmail.com
C++ is one of the reasons we can't seem to shake loose of the
historical misnaming of the byte type.
That makes no sense whatsoever. /C/ is one of the reasons why /C++/
can't shake the confusing definition of "byte" and other historical
oddities of C and C++ that do not appear in programming languages of
modern design. C++ is hindered by compatibility with C - that has
always been the case, and it was a design choice in early C++ to accept
that for the benefits of compatibility. C is not hindered by C++
compatibility.
James Kuyper
2017-05-23 12:00:21 UTC
Permalink
Raw Message
...
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
Post by James Kuyper
In this case, all you need to change is to explicitly cast the values
returned by malloc().
Wait. malloc() doesn't return a char * in C++?
Right - just like in C, malloc() returns void* in C++.
My memory was wrong about this.
Mea culpa.
My memory that I deliberately chose a char pointer instead of a
void pointer in that code because I couldn't do what I wanted with
a void pointer might be suspect, as well.
That depends. In both languages, you can't add an integer to a void*
pointer. The defined behavior of adding an integer to a pointer is to
move the pointer forward by a number of bytes equal to that integer
times the size of the pointed-at type. With a void*, there is NO
pointed-at type; with char* there is, and it has, by definition, a size
of exactly one byte.
Choosing a char* in order to allow pointer arithmetic is entirely
reasonable - and has nothing to do with any differences between C and C++.
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
Post by James Kuyper
That makes the code worse marginally worse, from
the point of view of C programming, but it gives the code precisely the
same defined behavior when using either language to compile it.
Seriously? In both languages? Exact same semantics?
Yes. I'm curious - did you have any particular semantic differences you
were expecting to occur? If so, what were they?
Well, what does adding one to a void pointer do?
It's a constraint violation in both languages.
Post by j***@gmail.com
My memory is that the compiler is supposed to tell me it doesn't
know how much 1 means relative to a void pointer, one way or another.
The implementation is required to generate a diagnostic, but neither C
nor C++ provide any requirements concerning how useful the diagnostic
message is for figuring out what the problem is.
Post by j***@gmail.com
And I think that has something to do with why I didn't put the cast
in, but who knows whether the current expression would work okay with
a cast.
Explicitly converting the result returned by malloc() to the target type
is mandatory in C++, because allowing such conversions to occur
implicitly is very dangerous in a language with function overloading.
It's permitted in C, but a poor idea, because it can hide certain
possible mistakes. Therefore, code written in the common subset must
cast that result.

Note that idiomatic C++ wouldn't use malloc(), and would therefore not
need a cast. It would use the "new" operator, or better yet, one of the
standard containers, such as std::array<> (which is relatively new) or
std::vector<>.

...
Post by j***@gmail.com
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
Can you cite even one example of such churn? I'm fairly familiar with
the C++ standard, and very familiar with the C standard, and I can't
think of any examples of cases where the C committee went to any effort
at all (much less an unreasonable effort) to maintain compatibility with
C++. While they are committed to a policy of avoiding creating
gratuitous incompatibilities between the languages, following that
policy has mainly served to modify the way in which new features were
added to the language (and even that hasn't happened very often). That
policy has not, in itself, lead to creating of any new features.

...
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
Oh, never mind. In C++, there has to be a deeper root pointer type than
in C. Or something like that.
Nope - void* is the closest thing either language has to a generic
pointer to object type. The key difference is that C++ depends heavily
upon operator overloading, which means that an implicit conversion from
void* to other pointer types would be excessively dangerous in C++.
That's what I meant to say, even though I seem to have said it in a way
that you interpreted otherwise.
If you would use language like "In C++, there has to be a deeper root
pointer type than in C", to express the idea that "In C++, the deepest
root pointer type is exactly the same type, and exactly as deep, as it
is in C", then I recommend reviewing your understanding of English.
"deeper" != "exactly as deep".
Post by j***@gmail.com
You see no problem with that. I see lots of problems.
In the context of writing code in the common subset, the fact that both
languages use the same exact type as their deepest root pointer type
would seem to make it easier to use the common subset, not harder.

The fact that C++ has stricter rules than C for working with that type
is a (very minor) problem for writing code in the common subset - but I
was not responding to a comment that mentioned that problem.

...
Post by j***@gmail.com
Post by James Kuyper
that conclusion. The biggest differences between the two languages have
been syntactic, not semantic. In most cases, syntax accepted as valid in
both languages has the same semantics in both languages.
I once went to considerable effort to write up a program demonstrating
every such case - there weren't many.
By your own admission, they were there.
I described virtually every single such case in just a few paragraphs.
Those cases are, for the most part, very obscure and trivial to deal
with. I never said the languages were identical - but some people say
that C is a subset of C++, and there's a lot of truth in that false
claim. What I said above is the actual truth to which that claim is a
coarse approximation.
Post by j***@gmail.com
Post by James Kuyper
'a' has the type "int" in C, and the type "char" in C++.
There's one.
C++ is one of the reasons we can't seem to shake loose of the
historical misnaming of the byte type.
As others have already pointed out, you've got cause and effect
backwards. Compatibility with C is what's keeping C++ from shaking loose
from that historical misnaming. As far as C itself is concerned, the
high importance that the committee attaches to backwards compatibility
is the main obstacle to changing that.
Post by j***@gmail.com
Post by James Kuyper
Structs, unions, and enumerations can all have tags.
Here we go.
Post by James Kuyper
In C, those tags
must always be accompanied by the 'struct', 'union' or 'enum' keywords.
Therefore, they have been given their own name space, separate from the
name space for ordinary identifiers. Thus, you can define a tag with the
same name as an object, and there's never any ambiguity. In C++, those
tags can be used without those keywords, so they can be confused with
ordinary identifiers. It is therefore not permitted to use the same
identifier as both a tag and as an object identifier in the same scope
in C++. You can demonstrate the difference in code with defined behavior
by creating situations where one declaration of an identifier hides a
declaration of that identifier with a different scope in C++, because
they are in the same name space, but fails to hide it in C, because they
are in different name spaces. I had to be careful when constructing my
demonstration, to avoid using the identifier in ways that would be a
syntax error or constraint violation for one of the two meanings. Real
life code would not be so careful; such code would almost certainly
cause a diagnostic for one reason or another.
This is probably at the root of the other error messages that compiling
as C++ produced.
I sincerely doubt it. The C syntax associated with declaring and using
struct types is perfectly legal C++, and in almost all cases has the
same meaning in both languages. It's C++ that allows some things that
aren't permitted by C. The issue I described above can only come up if
you use the same identifier to refer to two or more very different
things in the same scope. This is something most sane programmers avoid
doing, for the simple reason that it tends to be confusing. It's
relatively difficult to write code which does that without triggering
mandatory diagnostics in C. If you get error messages only when
compiling your code with C++, they're probably about some completely
different issue.

...
Post by j***@gmail.com
I might be wrong, but I don't want to try and find out how much
re-writing it would take to clear the union initialization errors I
hit.
If I had any idea what you were talking about, I might be better able to
address that issue. Could you provide examples of the error messages,
and the actual text of the code that triggered them?
Post by j***@gmail.com
Post by James Kuyper
Post by j***@gmail.com
Anyway, if you want to prove to me that my source code for that Forth
interpreter can be reasonably modified to compile okay as non-class
C++ source, I'll be interested in the results.
Since I'm not recommending such a change, I don't see much point in
proving that it can be done. "reasonably" is also a potential debating
point - you might consider some of the required changes unreasonable.
If I can't initialize those unions, I might as well throw that whole
project away.
I know of no reason why union initialization should be a problem with
C++. There's probably some other factor involved that you haven't
mentioned, which is why I'd like to see an example of code that triggers
those error messages, and the exact text of those messages.
Post by j***@gmail.com
It's only sixteen thousand lines of comment and code. Why not?
The languages are different. The fact that there is a subset usable
in some contexts does not mean they are not different.
Yes, they are different, but they do have a common subset that is usable
not only in "some" contexts, but in virtually all contexts where C
itself would be usable. And that means that C can, with only a small
amount of falsehood, be described as a subset of C++.
s***@casperkitty.com
2017-05-23 15:27:08 UTC
Permalink
Raw Message
Post by j***@gmail.com
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
A fundamental problem with C is that the Standard suggests that many
implementations will treat various actions in a fashion consistent with
a (likely useful) documented behavior of the execution environment, but
doesn't specify any means by which code can indicate that it requires
such behavior. Dialects which do so are are very good for some kinds of
low-level programming to which the core language would otherwise be
unsuitable. All that is necessary is that compiler writers recognize two
simple principles: (1) if it would in cost nothing for an implementation
to usefully guarantee the consequences of some action in the "general"
case (as distinct from all specific cases)[*], an implementation should do
so unless it a documents a compelling reason to do otherwise, and (2) the
fact that the Standard does not mandate such behavior should not be viewed,
in and of itself, as a "compelling reason" to do otherwise.

If implementers abide by that principle, there's no need to have the
Standard worry about nitty gritty details of what platform behaviors
should be regarded as useful in what application fields. Unfortunately,
many compiler writers have come to believe that the lack of a behavioral
mandate should be regarded as a compelling reason for a compiler to act
in nonsensical fashion in specific cases where it can identify that the
Standard would allow it.

[*] As an example of "general-case" behavior, given code like:

unsigned mulMod65536(uint16_t x, uint16_t y) { return (x*y) & 65535u; }

the "general" case would be the one where a compiler knows nothing about x
and y. In that case, returning the low 16 bits of the product without
regard for whether it is greater that 2147483647 would be faster than trying
to do anything else. In a specific cases where a compiler knows that
x>=39533 and y>=54321, the Standard would allow it to unconditionally
return 53981 without having to perform the multiply. Having the compiler
unconditionally return the low 16 bits of the result would have zero cost in
the general case, despite the possible cost in some specific cases.
Ian Collins
2017-05-23 19:17:30 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
Post by j***@gmail.com
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
A fundamental problem with C is that the Standard suggests that many
implementations will treat various actions in a fashion consistent with
a (likely useful) documented behavior of the execution environment, but
doesn't specify any means by which code can indicate that it requires
such behavior.
What does that, or what followed have to do with the alleged
"unreasonable efforts to keep C and C++ compatible"?
--
Ian
s***@casperkitty.com
2017-05-23 19:28:44 UTC
Permalink
Raw Message
Post by Ian Collins
Post by s***@casperkitty.com
Post by j***@gmail.com
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
A fundamental problem with C is that the Standard suggests that many
implementations will treat various actions in a fashion consistent with
a (likely useful) documented behavior of the execution environment, but
doesn't specify any means by which code can indicate that it requires
such behavior.
What does that, or what followed have to do with the alleged
"unreasonable efforts to keep C and C++ compatible"?
My intended point was that the "churn from the unreasonable efforts to
keep C and C++ compatible" is a rather minor factor compared with the
"unreasonable optimizations" alluded to.

The sentence to which I was replying seemed somewhat analogous to saying
that the Titanic sank because there were too many heavy cellos on board
(the fact that it hit an iceberg may also have contributed).
David Brown
2017-05-24 08:04:39 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
Post by Ian Collins
Post by s***@casperkitty.com
Post by j***@gmail.com
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
A fundamental problem with C is that the Standard suggests that many
implementations will treat various actions in a fashion consistent with
a (likely useful) documented behavior of the execution environment, but
doesn't specify any means by which code can indicate that it requires
such behavior.
What does that, or what followed have to do with the alleged
"unreasonable efforts to keep C and C++ compatible"?
My intended point was that the "churn from the unreasonable efforts to
keep C and C++ compatible" is a rather minor factor compared with the
"unreasonable optimizations" alluded to.
In other words, your "point" was almost totally and completely
irrelevant to the thread. The thread was about how C++ may or may not
have influenced C, and to what extent compatibility between the
languages is a good thing. Joel briefly mentioned "unreasonable
optimisations" in parenthesis - perhaps hoping to distract others from
the complete lack of evidence for his other claims. And you saw it as
an opportunity to repost, for the thousandth time, your favourite
complaint about multiplying uint16_t types.

For the sake of everyone in this group, would you /please/ stop doing
that? I regularly ignore your posts, because I know they are often just
the same old crap again and again. This is a shame, because you do
sometimes make useful points - and your example about multiplying
uint16_t types is interesting and important, and something C programmers
should know about. But we don't need it repeated in /every/ thread in
the newsgroup! Talk to the FAQ maintainer about getting it published
there, and leave it alone for the future.
Post by s***@casperkitty.com
The sentence to which I was replying seemed somewhat analogous to saying
that the Titanic sank because there were too many heavy cellos on board
(the fact that it hit an iceberg may also have contributed).
Richard Bos
2017-05-24 16:30:16 UTC
Permalink
Raw Message
Post by Ian Collins
Post by s***@casperkitty.com
Post by j***@gmail.com
C is not a stable language, in no small part because of the churn
in the standard induced by the unreasonable efforts to keep C and C++
"compatible". (Unreasonable optimizations get tangled up in that
churn, as well.)
A fundamental problem with C is that the Standard suggests that many
implementations will treat various actions in a fashion consistent with
a (likely useful) documented behavior of the execution environment, but
doesn't specify any means by which code can indicate that it requires
such behavior.
*Yawn*

No, it still doesn't.
Post by Ian Collins
What does that, or what followed have to do with the alleged
"unreasonable efforts to keep C and C++ compatible"?
Nothing. Nor with reality, as usual.

Richard
James R. Kuyper
2017-05-24 19:01:16 UTC
Permalink
Raw Message
On 05/20/2017 06:20 AM, ***@gmail.com wrote:
...
Post by j***@gmail.com
https://sourceforge.net/p/bif-c/code/HEAD/tree/trunk/
I finally took the time to look at your code, and discovered a key
problem. You need to be very clear what your target language is, and
them make sure your code is consistent with that choice. Here's why
that's a problem:

definition_header_s is a typedef for a struct type containing a flexible
array member named parameterLink. Unless you change that, your code has
no chance of being compatible with C++, or even with C90. However, it's
much worse than that. You've defined objects of that types with static
storage duration, with initializers for those objects that include
values to be placed in parameterLink.

Standard C doesn't allow that. For struct types containing flexible
array members, "... the size of the structure is as if the flexible
array member were omitted except that it may have more trailing padding
than the omission would imply." 6.7.2.1p18. Therefore, when an object of
such a type is allocated with static, thread, or automatic storage
duration, there is no space specially set aside for the elements of the
flexible array. If the struct has a lot of padding at the end, there
might incidentally be enough space for a few elements of the flexible
array. For example, if double has both a size of 8 and an alignment
requirement of 8, then the following struct type must have an alignment
requirement of at least 8:

struct {
double d;
char c;
char string[];
} dstring;

which implies that dstring must be big enough to store at least 7
elements in dstring.string. However, in general, you cannot count on
there being any space for any elements in the flexible array. Providing
initializers for those non-existent elements is therefore a constraint
violation.

All of the examples in the standard involving flexible array members
create the structs with allocated storage duration, in which case it's
possible to ensure that there's enough space for the desired number of
elements in the flexible array. It should be possible to reserve enough
space for a non-zero number of flexible array members by putting the
struct type in a union with a bigger object, but the standard contains
no examples of how to do that. The required syntax is functionally
equivalent to defining a fixed-length array rather than a flexible one,
while being more complicated than that approach, so I wouldn't recommend
trying that.

Since your code does compile for you, and for me when I use gcc with the
options you specify, it must be relying upon an extension to C which
allows you to provide initializers for the flexible array, and makes
sure that enough space is allocated to store all of those initializers.
This is an entirely reasonable extension, and I would have no objection
to changing standard C to support it - but as it stands, it is an
extension - your code has undefined behavior as far as standard C is
concerned.

The thing to keep in mind is that most C compilers do NOT fully conform
to any particular version of the C standard unless you explicitly tell
them too. Unless you select a -std= option for gcc, it implements GnuC,
not standard C. GnuC is a language distinct from, but very similar to,
standard C. It has many conforming extensions to C, but it also supports
many non-conforming extensions as well.

GnuC is one of the most widely used and most widely available C-like
alternatives to standard C. If you are willing to restrict the
portability of your code to GnuC, I can't really fault that decision -
it's a fairly popular one. But you need to be aware that this is what
you are doing. You should be making this decision explicitly, not as an
accident due to being unfamiliar with the differences between GnuC and
standard C. Most importantly, your documentation for your program should
explicitly state that you're relying upon GnuC.

Note that flexible array members are not supported by C++; this is a
legitimate example of a divergence between the two languages. However,
it is not a counter-example to my claim - there are alternatives to
using flexible array members that provide similar functionality, and
they can be implemented in a way that has the exact same defined
behavior in C and C++. However, those alternatives are not as flexible,
simple, or as elegant as the code you're currently using that relies
upon an extension to C. Using those alternatives would NOT count as
making your code better - flexible array members were invented precisely
because they are a better approach than those alternatives. Explaining
those alternatives is complicated, and I can't spare the time right now
to go into details. However, if you're really interested, I may be able
to find time later.

Removing your code's reliance on this extension would be complicated,
which is one reason I don't think you're likely to make that choice.
However, if you do want to change your code to use standard C rather
than GnuC, you have a couple of options:

1. Target C99, which allows you to continue using flexible array
members, but requires that you allocate the memory for them using
malloc(), calloc(), or realloc().

2. Target C90, and make use of the struct hack. De jure, the struct hack
has undefined behavior in all versions of both C and C++, so it isn't in
the common subset. However, de facto, the struct hack is so popular that
most implementations of C and C++ can be trusted to allow it to work.
Therefore, de facto, you would still be in the common subset of those
languages. You still need to allocate the required memory using
malloc(), calloc(), or realloc().
s***@casperkitty.com
2017-05-24 20:04:36 UTC
Permalink
Raw Message
Post by James R. Kuyper
Standard C doesn't allow that. For struct types containing flexible
array members, "... the size of the structure is as if the flexible
array member were omitted except that it may have more trailing padding
than the omission would imply." 6.7.2.1p18. Therefore, when an object of
such a type is allocated with static, thread, or automatic storage
duration, there is no space specially set aside for the elements of the
flexible array. If the struct has a lot of padding at the end, there
might incidentally be enough space for a few elements of the flexible
array. For example, if double has both a size of 8 and an alignment
requirement of 8, then the following struct type must have an alignment
struct {
double d;
char c;
char string[];
} dstring;
which implies that dstring must be big enough to store at least 7
elements in dstring.string. However, in general, you cannot count on
there being any space for any elements in the flexible array. Providing
initializers for those non-existent elements is therefore a constraint
violation.
What would you make of something like:

#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <stddef.h>
typedef struct { char const *st; uint16_t size; uint16_t arr[]; } foo;
typedef struct { char const *st; uint16_t size; uint16_t arr[8]; } foo8;
_Static_assert(offsetof(foo, arr) == offsetof(foo8,arr),
"Invalid array offset");

typedef union { foo8 f8; foo f; } foo8u;

foo8u myFooXX = {{"Hello",8,{1,2,3,4,5,6,7,8}}};
#define myFoo (myFooXX.f)

void useFoo(foo *p)
{
printf("(%s:", p->st);
for (int i=0; i < p->size; i++)
printf(" %d", p->arr[i]);
printf(")\n");
}
void test(void)
{
printf("Printing foo of size %d:\n", myFoo.size);
useFoo(&myFoo);
}

By my reading, the static assert could fail in a conforming implementation,
though I don't know of any where it would. On any implementation where the
static assert succeeds, the address of the flexible array member would be
guaranteed to coincide with that of the corresponding member in the fixed-
sized structure, which would have room for 8 elements.

I don't particularly like the #define, but unless code tries to use the
same name in a nested scope it shouldn't cause trouble. There are only
two problems I see with the approach:

1. The Standard as written would require that every different size of
structure which might be used by useFoo must be declared before it,
and unless code wants to use C11-specific designated-initializer
syntax it would as a practical measure have to define a separate
union type for each size of structure, which listed the structure of
that size as its first member.

2. The way gcc and clang interpret the Standard, there is no way to
implement such a construct with defined behavior, except through the
use of gcc-specific extensions.

Were it not for the first issue, client code could be written somewhat more
cleanly as:

// Library header
#define makeInitializedFoo(name, s,sz,...) \
union { struct { char const *st; uint16_t size; uint16_t arr[sz];}; foo f;} name = \
{{s,sz,__VA_ARGS__}}

// Client code
makeInitializedFoo(myFoo2, "myFoo", 4, {1,2,3,4});

I see no reason it shouldn't be practical to have the Standard require
compilers to recognize Common-Initial-Sequence aliasing between struct
types that are visible in a union *either* when a member is written *or*
when it's read back, or why compilers that are trying to be helpful
shouldn't recognize aliasing in such cases, but compiler writers demand
that programmers to jump through silly hoops and regard any code that
doesn't do so as "broken".
j***@gmail.com
2017-05-24 21:55:18 UTC
Permalink
Raw Message
Post by James R. Kuyper
...
Post by j***@gmail.com
https://sourceforge.net/p/bif-c/code/HEAD/tree/trunk/
I finally took the time to look at your code, and discovered a key
problem. You need to be very clear what your target language is, and
them make sure your code is consistent with that choice. Here's why
definition_header_s is a typedef for a struct type containing a flexible
array member named parameterLink. Unless you change that, your code has
no chance of being compatible with C++, or even with C90. However, it's
much worse than that. You've defined objects of that types with static
storage duration, with initializers for those objects that include
values to be placed in parameterLink.
Standard C doesn't allow that. For struct types containing flexible
array members, "... the size of the structure is as if the flexible
array member were omitted except that it may have more trailing padding
than the omission would imply." 6.7.2.1p18. Therefore, when an object of
such a type is allocated with static, thread, or automatic storage
duration, there is no space specially set aside for the elements of the
flexible array. If the struct has a lot of padding at the end, there
might incidentally be enough space for a few elements of the flexible
array. For example, if double has both a size of 8 and an alignment
requirement of 8, then the following struct type must have an alignment
struct {
double d;
char c;
char string[];
} dstring;
which implies that dstring must be big enough to store at least 7
elements in dstring.string. However, in general, you cannot count on
there being any space for any elements in the flexible array. Providing
initializers for those non-existent elements is therefore a constraint
violation.
All of the examples in the standard involving flexible array members
create the structs with allocated storage duration, in which case it's
possible to ensure that there's enough space for the desired number of
elements in the flexible array. It should be possible to reserve enough
space for a non-zero number of flexible array members by putting the
struct type in a union with a bigger object, but the standard contains
no examples of how to do that. The required syntax is functionally
equivalent to defining a fixed-length array rather than a flexible one,
while being more complicated than that approach, so I wouldn't recommend
trying that.
Since your code does compile for you, and for me when I use gcc with the
options you specify, it must be relying upon an extension to C which
allows you to provide initializers for the flexible array, and makes
sure that enough space is allocated to store all of those initializers.
This is an entirely reasonable extension, and I would have no objection
to changing standard C to support it - but as it stands, it is an
extension - your code has undefined behavior as far as standard C is
concerned.
The thing to keep in mind is that most C compilers do NOT fully conform
to any particular version of the C standard unless you explicitly tell
them too. Unless you select a -std= option for gcc, it implements GnuC,
not standard C. GnuC is a language distinct from, but very similar to,
standard C. It has many conforming extensions to C, but it also supports
many non-conforming extensions as well.
GnuC is one of the most widely used and most widely available C-like
alternatives to standard C. If you are willing to restrict the
portability of your code to GnuC, I can't really fault that decision -
it's a fairly popular one. But you need to be aware that this is what
you are doing. You should be making this decision explicitly, not as an
accident due to being unfamiliar with the differences between GnuC and
standard C. Most importantly, your documentation for your program should
explicitly state that you're relying upon GnuC.
Note that flexible array members are not supported by C++; this is a
legitimate example of a divergence between the two languages. However,
it is not a counter-example to my claim - there are alternatives to
using flexible array members that provide similar functionality, and
they can be implemented in a way that has the exact same defined
behavior in C and C++. However, those alternatives are not as flexible,
simple, or as elegant as the code you're currently using that relies
upon an extension to C. Using those alternatives would NOT count as
making your code better - flexible array members were invented precisely
because they are a better approach than those alternatives. Explaining
those alternatives is complicated, and I can't spare the time right now
to go into details. However, if you're really interested, I may be able
to find time later.
Removing your code's reliance on this extension would be complicated,
which is one reason I don't think you're likely to make that choice.
However, if you do want to change your code to use standard C rather
1. Target C99, which allows you to continue using flexible array
members, but requires that you allocate the memory for them using
malloc(), calloc(), or realloc().
2. Target C90, and make use of the struct hack. De jure, the struct hack
has undefined behavior in all versions of both C and C++, so it isn't in
the common subset. However, de facto, the struct hack is so popular that
most implementations of C and C++ can be trusted to allow it to work.
Therefore, de facto, you would still be in the common subset of those
languages. You still need to allocate the required memory using
malloc(), calloc(), or realloc().
I hope you don't mind, James, but I'll follow this up in the thread, 'conversion of code to common subset of C and C++ in my bif-c project (from "if statement with initializer")'.
Keith Thompson
2017-05-24 23:23:19 UTC
Permalink
Raw Message
[108 lines deleted]
Post by j***@gmail.com
I hope you don't mind, James, but I'll follow this up in the thread,
'conversion of code to common subset of C and C++ in my bif-c project
(from "if statement with initializer")'.
Joel, if you post a followup to a long article, please don't quote the
whole thing; trim anything that you're not replying to. Depending on
what newsreader someone is using, they might have to scroll down past
the entire quoted content to get to the new text.

Also, please format your lines to less than about 80 columns (70-72 is
probably ideal). My newsreader, for example, will split long lines, but
not at word boundaries.

Thanks.
--
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"
Richard Bos
2017-05-21 18:01:50 UTC
Permalink
Raw Message
Post by David Brown
Post by j***@gmail.com
Going from C++ to C, of course, is a no-starter.
Twenty years ago, I put a lot of effort into learning how to write in the
subset. That effort derailed a couple of my private projects and at least
one project at work. It would not be going too far to say trying to use
that subset may have been one of the factors in my being asked to quit
that company.
I really cannot comprehend why you think this is such a big deal. It is
/not/ hard to write C in a C++ compatible subset.
It is easy - and so is using goto with abandon. Neither is good
practice.

If you want to write C, write C. If you want to write C++, write C++.
Don't write hobbled C++ and convince yourself that you're writing nice,
sleek C.

Richard
David Brown
2017-05-22 08:04:45 UTC
Permalink
Raw Message
Post by Richard Bos
Post by David Brown
Post by j***@gmail.com
Going from C++ to C, of course, is a no-starter.
Twenty years ago, I put a lot of effort into learning how to write in the
subset. That effort derailed a couple of my private projects and at least
one project at work. It would not be going too far to say trying to use
that subset may have been one of the factors in my being asked to quit
that company.
I really cannot comprehend why you think this is such a big deal. It is
/not/ hard to write C in a C++ compatible subset.
It is easy - and so is using goto with abandon. Neither is good
practice.
Code that can be compiled as either C or C++ can be a requirement for a
project - and writing it that way is therefore not merely /good/, but
essential. Yes, I have worked on several projects where all or
significant parts of the code must work as C and as C++. In the world
of embedded programming, it is not an uncommon requirement. This is in
the real world, where programmers get paid to write code that customers
ask for, rather than being free to create works of beauty like a painter
with a field of sunflowers, and to reject casts of malloc() like an ugly
weed.

On the other hand, I have never been given a specification that asked
for code that "uses goto with abandon".
Post by Richard Bos
If you want to write C, write C. If you want to write C++, write C++.
In general, yes - that is the most common requirement.

But as a professional programmer, you may also be asked to write code in
the common subset of C and C++ - just as you may be asked to write code
in C90 rather than C11, or C++03 rather than C++14, even though you know
you could write clearer or more elegant code in later versions of these
languages.

Fortunately, writing code that is in a common subset of, for example,
C99 and C++98, is easy. The result will not differ much from what you
would write in pure C99. You are usually using exactly the same
development tools for the job - the same compiler (but slightly
different flags), the same editor/IDE, etc. I certainly greatly prefer
restricting my C programming to the C++ compatible subset than
restricting it to the C90 subset.
Post by Richard Bos
Don't write hobbled C++ and convince yourself that you're writing nice,
sleek C.
This is not programming in a hobbled or restricted C++ - it is
programming in a marginally restricted C. The common subset is perhaps
90% of C, but it is a mere 20% of C++03 and 10% of C++11. Code written
in the common subset is just plain C with a few minor restrictions, but
it is very far from how you would solve the same problem writing it in C++.
Post by Richard Bos
Richard
Tim Rentsch
2017-05-24 15:10:27 UTC
Permalink
Raw Message
Post by David Brown
[...]
I really cannot comprehend why you think this is such a big deal.
It is /not/ hard to write C in a C++ compatible subset. [...]
Herein lies the crux the matter: you admit you don't understand
the other point of view, yet you insist with absolute certainty
that the other point of view is wrong. There's no reason to read
any further.
David Brown
2017-05-24 15:54:42 UTC
Permalink
Raw Message
Post by Tim Rentsch
Post by David Brown
[...]
I really cannot comprehend why you think this is such a big deal.
It is /not/ hard to write C in a C++ compatible subset. [...]
Herein lies the crux the matter: you admit you don't understand
the other point of view, yet you insist with absolute certainty
that the other point of view is wrong. There's no reason to read
any further.
Yes, I am convinced the "other point of view" is wrong in this case. I
have asked multiple times for examples, and explanations, and every time
so far it has been a matter of misunderstandings, or the poster (Joel
Rees in this case) has simply failed to give /any/ examples to show his
point.

If someone were to post some reasons why they think it is /hard/ to
write C in a C++ compatible manner, they may convince me. If someone
will post examples or reasons why they think C has suffered because of
keeping compatibility with C++, they may convince me of that too. If
someone will post examples of the supposed semantic differences between
code that is correct C and correct C++, and not just artificial
pathological cases, then again I may be convinced.

(Note that I am perfectly happy to accept that if compatibility with C++
is not an issue, then you can write "better" C - such as by not casting
the results of malloc(). I don't believe it is common that the
non-C++-compatible C code will be /significantly/ "better" than
compatible C code would be, but certainly /somewhat/ better.)


I wrote that I cannot comprehend the other viewpoint here, because I
cannot comprehend it. If /you/ think it is /hard/ to write C in a C++
compatible manner, then please enlighten me as to why.
Scott Lurndal
2017-05-19 12:34:27 UTC
Permalink
Raw Message
It is certainly not uncommon to deal with more than one different
programming language. But I strongly suspect that if you ask C
programmers what other languages they use regularly, C++ will come up
more often than Perl or Java - and certainly more often than Ada or
Forth. (On the other hand, if you ask Ada or Forth programmers what
other languages they use, C will be common - it is not symmetrical.)
I strongly suspect that if you ask C++ programmers what other languages
they work with, they will say, Java, javascript, Python, Ruby, and C.
But they would likely be wrong about C.
That I doubt. Working with C in kernel space and C++ in the application
space is a common combination, at least in the embedded Linux world I've
inhabited for the past few years.
Although I've written parts of two kernels and a hypervisor that were
written in a subset of C++ (no STL, no exceptions, no RTTI). Basically
C with classes, which is my preferred dialect of C++.
Thiago Adams
2017-05-19 13:37:37 UTC
Permalink
Raw Message
Post by Scott Lurndal
It is certainly not uncommon to deal with more than one different
programming language. But I strongly suspect that if you ask C
programmers what other languages they use regularly, C++ will come up
more often than Perl or Java - and certainly more often than Ada or
Forth. (On the other hand, if you ask Ada or Forth programmers what
other languages they use, C will be common - it is not symmetrical.)
I strongly suspect that if you ask C++ programmers what other languages
they work with, they will say, Java, javascript, Python, Ruby, and C.
But they would likely be wrong about C.
That I doubt. Working with C in kernel space and C++ in the application
space is a common combination, at least in the embedded Linux world I've
inhabited for the past few years.
Although I've written parts of two kernels and a hypervisor that were
written in a subset of C++ (no STL, no exceptions, no RTTI). Basically
C with classes, which is my preferred dialect of C++.
My preferred dialect of C++ is C99 with templates.

It solves the problem of algorithms and containers.
I think the concept of classes is a little broken and inconsistent.

The following sample in C++ have a lot of samples
of the features I would use.

template <class T>
T* New() {
T* p = (T*)malloc(sizeof T);
if (p) {
*p = T(); //(T){}
}
return p;
}

template <class T>
void Delete(T* p) {
if (p) {
Destroy(p);
free(p);
}
}

typedef char* String;

void Destroy(String* s) {
free(*s);
}

struct Node {
String Name = NULL;
Node* pNext = NULL;
};

void Destroy(Node* p) {
Destroy(&p->Name);
}

template <class T>
struct List {
T* pHead = NULL;
T* pTail = NULL;
};

template <class T>
void Destroy(List<T>* p) {
T* pItem = p->pHead;
while (pItem) {
T* pCurrent = pItem;
pItem = pItem->pNext;
Delete(pCurrent);
}
}

template <class T>
void Add(List<T>* pList, T* pItem) {
if (pList->pHead == NULL) {
pList->pHead = pItem;
} else {
pList->pTail->pNext = pItem;
;
}
pList->pTail = pItem;
}

int main() {
Node* p = New<Node>();

List<Node> list = List<Node>(); //{}

Add(&list, p);
p = NULL;

Delete(p);
Destroy(&list);

return 0;
}


And here the version using C99


static inline void* allocate_and_copy(void *s, size_t n)
{
void* pNew = malloc(n);
if (pNew)
{
memcpy(pNew, s, n);
}

return pNew;
}

#define New(...) allocate_and_copy(&(__VA_ARGS__), sizeof(__VA_ARGS__))


#define Delete(T, p)\
while (p) {\
T##_Destroy(p);\
free(p);\
break;\
}


typedef char* String;

void String_Destroy(String* s) {
free(*s);
}

typedef struct Node {
String Name /*= NULL*/;
struct Node* pNext /*= NULL*/;
} Node;

void Node_Destroy(Node* p) {
String_Destroy(&p->Name);
}



#define List(T)\
struct List {\
T* pHead;\
T* pTail;\
}

#define List_Destroy(T, p)\
while ((p)->pHead) {\
T* pCurrent = (p)->pHead;\
(p)->pHead = (p)->pHead->pNext;\
Delete(T, pCurrent);\
}



#define Add(pList, pItem) \
if ((pList)->pHead == NULL) {\
(pList)->pHead = (pItem); \
(pList)->pTail = (pItem);\
}\
else {\
(pList)->pTail->pNext = (pItem); \
(pList)->pTail = (pItem);\
}


int main() {
Node* p = New((Node){0});

List(Node) list = { 0 };

Add(&list, p);
p = NULL;

Delete(Node, p);
List_Destroy(Node, &list);

return 0;
}
s***@casperkitty.com
2017-05-19 14:33:17 UTC
Permalink
Raw Message
If the spec had indicated that storing a value x to a "bool" will cause the
system to either store a value which will evaluate as zero if x==0, will
evaluate to 1 if x==1, will evaluate as an Unspecified odd integer in
other cases where (x & 1) is non-zero, and will evaluate as an Unspecified
value in all other cases, that would in some cases have allowed compilers
to generate more efficient code for "bool" than for "char" and would in no
case require a compiler to generate worse code. Such a definition would also
have been compatible with pre-existing "bit" types that were supported by a
number of compilers.
Yes, that is /exactly/ what the C standard needs - a totally
impenetrable half-defined specification that leaves both users and
implementers totally mystified, and unable to rely on anything.
There would be four defined behaviors:

Store 0 -- Type reads as 0
Store 1 -- Type reads as 1
Store other odd number -- Type reads as Unspecified odd number
Store other even number -- Type reads as Unspecified integer

I don't think there's anything impenetrable about that, given that
the most common actions would be storing 0 or storing 1, and that
the above would be consistent with any existing Boolean-style types
that existing compilers support.

On some platforms "x |= 1;" is faster than "x = 1;", and the above
definition would allow a compiler to substitute the former for the
latter whether or not it normalizes bool variables when read. On
platforms where testing (x & 1) is as fast or faster than testing
for (x != 0), and which would normalize bool values when read in
other contexts, it may be possible to replace x=0 with x<<= 1, and
either x=!x or x^=1 with x++.
David Brown
2017-05-19 16:17:16 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
If the spec had indicated that storing a value x to a "bool" will cause the
system to either store a value which will evaluate as zero if x==0, will
evaluate to 1 if x==1, will evaluate as an Unspecified odd integer in
other cases where (x & 1) is non-zero, and will evaluate as an Unspecified
value in all other cases, that would in some cases have allowed compilers
to generate more efficient code for "bool" than for "char" and would in no
case require a compiler to generate worse code. Such a definition would also
have been compatible with pre-existing "bit" types that were supported by a
number of compilers.
Yes, that is /exactly/ what the C standard needs - a totally
impenetrable half-defined specification that leaves both users and
implementers totally mystified, and unable to rely on anything.
Store 0 -- Type reads as 0
Store 1 -- Type reads as 1
Store other odd number -- Type reads as Unspecified odd number
Store other even number -- Type reads as Unspecified integer
I don't think there's anything impenetrable about that,
If you can write it in that simple manner, why not do so in the first place?


But no, having a variety of half-specified behaviours in different
circumstances is /not/ helpful. It would be one thing to say that only
storing 0 or 1 is specified and everything else is undefined or
implementation defined. Here, however, your specifications give /no/
benefit beyond that to the the user, and just add work for the implementer.
Post by s***@casperkitty.com
given that
the most common actions would be storing 0 or storing 1, and that
the above would be consistent with any existing Boolean-style types
that existing compilers support.
On some platforms "x |= 1;" is faster than "x = 1;",
Really? Which ones?
Post by s***@casperkitty.com
and the above
definition would allow a compiler to substitute the former for the
latter whether or not it normalizes bool variables when read. On
platforms where testing (x & 1) is as fast or faster than testing
for (x != 0), and which would normalize bool values when read in
other contexts, it may be possible to replace x=0 with x<<= 1, and
either x=!x or x^=1 with x++.
None of that is remotely helpful on any platforms I have heard of.
s***@casperkitty.com
2017-05-19 16:53:40 UTC
Permalink
Raw Message
Post by David Brown
Post by s***@casperkitty.com
Store 0 -- Type reads as 0
Store 1 -- Type reads as 1
Store other odd number -- Type reads as Unspecified odd number
Store other even number -- Type reads as Unspecified integer
I don't think there's anything impenetrable about that,
If you can write it in that simple manner, why not do so in the first place?
What I wrote was pretty much the above, except in English rather than
tabular form.
Post by David Brown
But no, having a variety of half-specified behaviours in different
circumstances is /not/ helpful. It would be one thing to say that only
storing 0 or 1 is specified and everything else is undefined or
implementation defined. Here, however, your specifications give /no/
benefit beyond that to the the user, and just add work for the implementer.
An implementation could treat "bool" as synonymous with "char", or "unsigned
char", or "uint32_t", or an addressable bit on processors which have such
things, or any of a number of other possibilities, at its leisure. How is
that making "more work" for an implementation.
Post by David Brown
Post by s***@casperkitty.com
given that
the most common actions would be storing 0 or storing 1, and that
the above would be consistent with any existing Boolean-style types
that existing compilers support.
On some platforms "x |= 1;" is faster than "x = 1;",
Really? Which ones?
The PIC architecture has bit-set and bit-clear instructions, as does the
65C02 [vs the NMOS 6502], some of the Motorola 68xx family members, etc.
In addition, if the carry flag is known to hold the value that should be
stored into a boolean value, and if testing the LSB would be as fast as
testing for zero, the carry flag can be stored using a rotate-left
instruction without having to know the location's previous contents.
Post by David Brown
Post by s***@casperkitty.com
and the above
definition would allow a compiler to substitute the former for the
latter whether or not it normalizes bool variables when read. On
platforms where testing (x & 1) is as fast or faster than testing
for (x != 0), and which would normalize bool values when read in
other contexts, it may be possible to replace x=0 with x<<= 1, and
either x=!x or x^=1 with x++.
None of that is remotely helpful on any platforms I have heard of.
A compiler for any platform where the above wouldn't be helpful would be
free to simply treat "bool" as synonymous with "unsigned char", "uint32_t",
or any other integer type as convenient. Given the variables:

uint64_t x;
bool y;
uint32_t z;

On many systems the assignment y=x; would be faster under the rules I
describe than under the existing rules. If a programmer wants to have
the compiler check all bits of x, the programmer could write y=(x!=0) or
y=!!x, but if the programmer knows that my described behavior would meet
requirements in all cases the programmer could just write "y=x;".

On the flip side, if "y" were defined as "unsigned char;", then given
"y=x; z=y;" a compiler which had been keeping all variables in registers
would be required to mask off all but the bottom 8 bits. My rules would
give the compiler the option to skip that masking.
David Brown
2017-05-19 17:20:47 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
Post by David Brown
Post by s***@casperkitty.com
Store 0 -- Type reads as 0
Store 1 -- Type reads as 1
Store other odd number -- Type reads as Unspecified odd number
Store other even number -- Type reads as Unspecified integer
I don't think there's anything impenetrable about that,
If you can write it in that simple manner, why not do so in the first place?
What I wrote was pretty much the above, except in English rather than
tabular form.
Well, it was in "Supercat English" - a form of writing that tries to put
as much as possible in a single sentence.
Post by s***@casperkitty.com
Post by David Brown
But no, having a variety of half-specified behaviours in different
circumstances is /not/ helpful. It would be one thing to say that only
storing 0 or 1 is specified and everything else is undefined or
implementation defined. Here, however, your specifications give /no/
benefit beyond that to the the user, and just add work for the implementer.
An implementation could treat "bool" as synonymous with "char", or "unsigned
char", or "uint32_t", or an addressable bit on processors which have such
things, or any of a number of other possibilities, at its leisure. How is
that making "more work" for an implementation.
It is more work for the implementation if the user is expecting to get
anything useful out of the "bool". If you are happy with using a "char"
or an "int" for your fake booleans, just use them. Don't introduce
something extra complicated that has no benefits.
Post by s***@casperkitty.com
Post by David Brown
Post by s***@casperkitty.com
given that
the most common actions would be storing 0 or storing 1, and that
the above would be consistent with any existing Boolean-style types
that existing compilers support.
On some platforms "x |= 1;" is faster than "x = 1;",
Really? Which ones?
The PIC architecture has bit-set and bit-clear instructions, as does the
65C02 [vs the NMOS 6502], some of the Motorola 68xx family members, etc.
If you stick to proper C booleans, then "x = 1" can be implemented by
the same bit set instruction. x |= 1 is /not/ faster for bools. But I
will agree that it is faster if "x" is another 8-bit integer type.

Of course, the 6502 is a dead architecture, and has been for a couple of
decades. And the PIC is a brain-dead architecture, and has been since
its conception - no one really expects proper C to work on a PIC. You
use partially compliant compilers for old C standards and write in a
special dialect of PIC-C if you want to write efficient code.

It would be absolute madness to make standard C less efficient on every
other processor simply to suit the PIC, even if supercat-bools /were/
more efficient than real C _Bool types on those devices.
Post by s***@casperkitty.com
In addition, if the carry flag is known to hold the value that should be
stored into a boolean value, and if testing the LSB would be as fast as
testing for zero, the carry flag can be stored using a rotate-left
instruction without having to know the location's previous contents.
Post by David Brown
Post by s***@casperkitty.com
and the above
definition would allow a compiler to substitute the former for the
latter whether or not it normalizes bool variables when read. On
platforms where testing (x & 1) is as fast or faster than testing
for (x != 0), and which would normalize bool values when read in
other contexts, it may be possible to replace x=0 with x<<= 1, and
either x=!x or x^=1 with x++.
None of that is remotely helpful on any platforms I have heard of.
A compiler for any platform where the above wouldn't be helpful would be
free to simply treat "bool" as synonymous with "unsigned char", "uint32_t",
uint64_t x;
bool y;
uint32_t z;
On many systems the assignment y=x; would be faster under the rules I
describe than under the existing rules.
Assignment is less common than testing or using the bools - thus the
bool type was made so that assignment is always 0 or 1, giving the
compiler more optimisation opportunities when using it. That is the
whole point of _Bool ! Programmers already had "unsigned char", "int",
and other types that could be used as fake booleans if that were more
efficient.
Post by s***@casperkitty.com
If a programmer wants to have
the compiler check all bits of x, the programmer could write y=(x!=0) or
y=!!x, but if the programmer knows that my described behavior would meet
requirements in all cases the programmer could just write "y=x;".
On the flip side, if "y" were defined as "unsigned char;", then given
"y=x; z=y;" a compiler which had been keeping all variables in registers
would be required to mask off all but the bottom 8 bits. My rules would
give the compiler the option to skip that masking.
s***@casperkitty.com
2017-05-19 17:45:18 UTC
Permalink
Raw Message
Post by David Brown
Post by s***@casperkitty.com
Post by s***@casperkitty.com
Store 0 -- Type reads as 0
Store 1 -- Type reads as 1
Store other odd number -- Type reads as Unspecified odd number
Store other even number -- Type reads as Unspecified integer
An implementation could treat "bool" as synonymous with "char", or "unsigned
char", or "uint32_t", or an addressable bit on processors which have such
things, or any of a number of other possibilities, at its leisure. How is
that making "more work" for an implementation.
It is more work for the implementation if the user is expecting to get
anything useful out of the "bool". If you are happy with using a "char"
or an "int" for your fake booleans, just use them. Don't introduce
something extra complicated that has no benefits.
Any code which would work with the type as I described would work with any
like-sized unsigned type on any system, or signed type on two's-complement
systems. Store an odd value into an integer type and one will read back an
odd value.
Post by David Brown
Post by s***@casperkitty.com
A compiler for any platform where the above wouldn't be helpful would be
free to simply treat "bool" as synonymous with "unsigned char", "uint32_t",
uint64_t x;
bool y;
uint32_t z;
On many systems the assignment y=x; would be faster under the rules I
describe than under the existing rules.
Assignment is less common than testing or using the bools - thus the
bool type was made so that assignment is always 0 or 1, giving the
compiler more optimisation opportunities when using it. That is the
whole point of _Bool ! Programmers already had "unsigned char", "int",
and other types that could be used as fake booleans if that were more
efficient.
The most common operation on bool objects is probably testing, but
assignment is almost certainly more common than any other numerical usage.
With bool as defined, if the result of an int computation is assigned to a
bool, a compiler must expend effort normalizing the result to 0 or 1.

I can't think of any situations where a compiler could generate more
efficient machine code from a program that used "bool", versus one that
replaced "bool" with the most efficient integer type, but yielded the
same defined behaviors. Can you offer any situations of code where that
would be the case?
s***@casperkitty.com
2017-05-19 18:27:53 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
The most common operation on bool objects is probably testing, but
assignment is almost certainly more common than any other numerical usage.
With bool as defined, if the result of an int computation is assigned to a
bool, a compiler must expend effort normalizing the result to 0 or 1.
I mean with "bool" as defined by the Standard.

A compiler could satisfy all the requirements for my boolean type simply
by adding a "bool.h" file containing

#ifndef __bool_def
#define __bool_def
typedef unsigned char bool;
#endif

and adding the following to the "extensions" section of its documentation:

As an extension, this compiler will tread types "bool" and
"unsigned char" as compatible, and will likewise regard as
compatible types derived from them, such as "bool*" and
"unsigned char*".

That's all. I'm not sure why that would be harder than supporting the
"bool" type as defined by C99.
Keith Thompson
2017-05-19 18:32:57 UTC
Permalink
Raw Message
***@casperkitty.com writes:
[...]
Post by s***@casperkitty.com
Store 0 -- Type reads as 0
Store 1 -- Type reads as 1
Store other odd number -- Type reads as Unspecified odd number
Store other even number -- Type reads as Unspecified integer
I don't think there's anything impenetrable about that, given that
the most common actions would be storing 0 or storing 1, and that
the above would be consistent with any existing Boolean-style types
that existing compilers support.
If this were to be standardized, presumably these behaviors would be
stated in terms of conversions, not storing values.

It's substantially more complicated than the current behavor for _Bool,
which converts any zero value to 0 and any non-zero value to 1.
Post by s***@casperkitty.com
On some platforms "x |= 1;" is faster than "x = 1;", and the above
definition would allow a compiler to substitute the former for the
latter whether or not it normalizes bool variables when read. On
platforms where testing (x & 1) is as fast or faster than testing
for (x != 0), and which would normalize bool values when read in
other contexts, it may be possible to replace x=0 with x<<= 1, and
either x=!x or x^=1 with x++.
The distinction between odd and even values seems bizarre at first
glance, and IMHO stays that way even given the stated rationale.
If I were going to use a type with your proposed semantics, I
wouldn't depend on that, I'd very carefully avoid writing code
that's affected by it.

As far as I'm concerned, Boolean values have *meanings*. The meaning
of a Boolean value is either true or false. The current conversion
semantics for _Bool are convenient for programmers; for example if
I say

_Bool b = isalpha('a');

I know that `if (b)` will succeed; with your semantics, I don't
know that. I can even use a cast to _Bool as a normalization
operator, like `!!`.

If you can demonstrate a non-contrived program whose execution
speed is *significantly* faster with your semantics than with the
standard semantics, you might have a point.

For the most part, I care far more about correctness than speed.
And if I really need to shave a few instructions, nothing stops me
from using an object of type int or unsigned char as a Boolean if
I happen to know that doing so will result in measurably and
significantly faster code.
--
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-05-19 19:30:56 UTC
Permalink
Raw Message
Post by Keith Thompson
If this were to be standardized, presumably these behaviors would be
stated in terms of conversions, not storing values.
The primary notion is that conversions may truncate the value stored to
any convenient integer type, or it may regard any non-zero value as
not-zero. Further, the truncation width may vary depending upon, e.g.
whether an object is stored in a register or memory.
Post by Keith Thompson
It's substantially more complicated than the current behavor for _Bool,
which converts any zero value to 0 and any non-zero value to 1.
Unlike the _Bool type, my behavior would be specified by any integer type
of any width, or any pre-existing type which happened to behave like _Bool.
Post by Keith Thompson
The distinction between odd and even values seems bizarre at first
glance, and IMHO stays that way even given the stated rationale.
If I were going to use a type with your proposed semantics, I
wouldn't depend on that, I'd very carefully avoid writing code
that's affected by it.
Truncating an odd number to any width will yield an odd number, which will
of course be non-zero. Truncating an even number to some width may yield
either a zero or non-zero result, depending upon the number and the width
in question. If an implementation happens to normalize non-zero values to
1 (which would be allowed but not required), an even number might become
an odd number.

Consider the effect of "int x; ... bool y,z; y=x; ... z=y;" if y happens to
be stored in a register and z is stored in RAM. Under my rules, a compiler
could just copy x to y, and then copy the lower byte of y to z. If y and
z were of type "unsigned char", a compiler would have to mask off the lower
byte of x when copying to y, likely adding an extra instruction.
Post by Keith Thompson
As far as I'm concerned, Boolean values have *meanings*. The meaning
of a Boolean value is either true or false. The current conversion
semantics for _Bool are convenient for programmers; for example if
I say
_Bool b = isalpha('a');
I know that `if (b)` will succeed; with your semantics, I don't
know that. I can even use a cast to _Bool as a normalization
operator, like `!!`.
I would blame any weakness in the above on the looseness of the definition
for "isalpha". Given that !! is already available as a normalization
operator, I'm not sure why a cast to _Bool is somehow "better". If a
function "func" isn't known to always return 0 or 1, but code requires that
variable "flag" does, I would regard:

flag = !!func();

as superior to

flag = func();

even if "flag" happens to be a boolean, since the former pattern would
let the reader know that the programmer expected that func() might return
values other than 0 or 1.
Post by Keith Thompson
If you can demonstrate a non-contrived program whose execution
speed is *significantly* faster with your semantics than with the
standard semantics, you might have a point.
Can you offer any where the C99 semantics would offer any performance
benefit over the fastest integer type? Having a new type which never
behaves slower faster than existing types but sometimes behaves slower,
and offers no new semantics which could not readily be achieved better
using existing types, doesn't seem worthwhile. I could accept that my
proposed type would offer insufficient benefits over other types like
"unsigned char" as to make it worthwhile, but the cost of implementation
would be so low that the level of benefit required to justify it should
likewise be low.
Keith Thompson
2017-05-19 20:45:29 UTC
Permalink
Raw Message
[...]
Post by s***@casperkitty.com
Post by Keith Thompson
If you can demonstrate a non-contrived program whose execution
speed is *significantly* faster with your semantics than with the
standard semantics, you might have a point.
Can you offer any where the C99 semantics would offer any performance
benefit over the fastest integer type?
No, but that doesn't address my question. I asked about a program that
would be *significantly* faster. Your question would be relevant if the
goal were to provide a type on which operations are at least as fast as
corresponding operations on any existing pre-C99 type, but that was
never the goal of _Bool. And those existing types are still there.
Post by s***@casperkitty.com
Having a new type which never
behaves slower faster than existing types but sometimes behaves slower,
and offers no new semantics which could not readily be achieved better
using existing types, doesn't seem worthwhile.
It's not a "new type"; it's been in the standard for 18 years.
The semantics of _Bool include the fact that a conversion to _Bool
always yields a well-defined false or true value. No doubt there's
some cost to that. I do not believe that cost is significant.
Post by s***@casperkitty.com
I could accept that my
proposed type would offer insufficient benefits over other types like
"unsigned char" as to make it worthwhile, but the cost of implementation
would be so low that the level of benefit required to justify it should
likewise be low.
Are you proposing to add yet another Boolean type to the language, one
that would have to coexist with _Bool while providing weaker conversion
semantics? In my humble opinion, the level of benefit of such a type
would be negligible.

If you want to add `typedef unsigned char mybool;` to your own code,
feel free.
--
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"
James R. Kuyper
2017-05-19 16:39:21 UTC
Permalink
Raw Message
...
Post by j***@gmail.com
You can take almost any C program that has no syntax errors, constraint
violations, or undefined behavior, and, with only minor modifications,
convert it into code that has precisely the same defined behavior,
whether compiled as C code or as C++ code. That would not be true if the
languages had diverged as badly as you're suggesting.
Each version of the C standard since C90 has added features that have no
counterpart in C++, but later versions of C++ have often added some of
those same features. The ones that haven't yet been added to C++, are
relatively few, and not yet widely used. The biggest exceptions are
things like designated intializers and compound literals, but those are
merely convenience features for which there exists less convenient ways
to write the same thing that do have the same meaning in C and in C++.
Okay, you can write usable source in the mutual subset.
My statement was MUCH stronger than your re-statement. It's not just
that there's some small amount of usable code that can be written in the
mutual subset. Almost anything that can be written in C can be written
in the mutual subset. Furthermore, the modifications that would be
needed will generally be quite minor.
Post by j***@gmail.com
I'm not sure that some of my source that I think (heh) is valid C will
be compilable as C++ without some serious dinking in the details.
I'm curious - what features does your C code possess that makes you
think it would be difficult to modify it to compile under C++ with the
same behavior?
Tim Rentsch
2017-05-20 01:43:56 UTC
Permalink
Raw Message
[...]
If what you want is struct-objects that are always referred to by
pointer, this can be done in C as it (mostly) is today. All that's
missing is a little bit of syntactic sugar so method calls can be
written 'foo->method()' instead of, eg, 'Whatsit_method( foo )'.
We don't really need any new semantics, as the mapping between the
two is straightforward. Note that this includes ctors and dtors,
since always using pointers implies that dtors must be invoked
explicitly.
And we can do `foo->method(foo)` in C. It just requires `method` to
be a member of pointer-to-function type, and some way to ensure that
the struct object that foo points to has been properly initialized.
(`foo->method()` doesn't work because there's no implicit `this`
as there is in C++.)
Yes, it could be done that way, but probably wouldn't be, because
the space costs are too high: a pointer-to-function would be
needed in every object for every method. Adding one level of
indirection allows only one pointer for each struct, plus a
single instance of a struct with method function pointers, eg,
foo->m->method( foo )
Even though this way is better than the previous one, neither is
really acceptable for large-scale deployment. The reason is
having to duplicate the object being sent to (ie, in the sense of
having to name it twice, eg, the token 'foo' in this case).
In terms of usability having the syntactic sugar for method
calls is pretty much an essential component here.
The technique described by Keith is widely used in kernels,
particularity in module and driver interfaces. The space overhead is
a cost worth bearing when you want a modular interface and there
aren't too many objects. [...]
Yes, I didn't mean to imply it's unworkable in all
contexts. If the number pointers is small, or the
number of objects is small, or especially if both
are small, then it's not too bad. But it doesn't
scale to the general case of lots of pointers and
lots of objects, which is (at least potentially)
true in the case that was under discussion.
Ian Collins
2017-05-20 10:06:01 UTC
Permalink
Raw Message
Post by Tim Rentsch
And we can do `foo->method(foo)` in C. It just requires `method` to
be a member of pointer-to-function type, and some way to ensure that
the struct object that foo points to has been properly initialized.
(`foo->method()` doesn't work because there's no implicit `this`
as there is in C++.)
Yes, it could be done that way, but probably wouldn't be, because
the space costs are too high: a pointer-to-function would be
needed in every object for every method. Adding one level of
indirection allows only one pointer for each struct, plus a
single instance of a struct with method function pointers, eg,
foo->m->method( foo )
Even though this way is better than the previous one, neither is
really acceptable for large-scale deployment. The reason is
having to duplicate the object being sent to (ie, in the sense of
having to name it twice, eg, the token 'foo' in this case).
In terms of usability having the syntactic sugar for method
calls is pretty much an essential component here.
The technique described by Keith is widely used in kernels,
particularity in module and driver interfaces. The space overhead is
a cost worth bearing when you want a modular interface and there
aren't too many objects. [...]
Yes, I didn't mean to imply it's unworkable in all
contexts. If the number pointers is small, or the
number of objects is small, or especially if both
are small, then it's not too bad. But it doesn't
scale to the general case of lots of pointers and
lots of objects, which is (at least potentially)
true in the case that was under discussion.
XView scaled pretty well :)
--
Ian
Tim Rentsch
2017-05-26 22:54:55 UTC
Permalink
Raw Message
Post by Ian Collins
Post by Tim Rentsch
And we can do `foo->method(foo)` in C. It just requires `method` to
be a member of pointer-to-function type, and some way to ensure that
the struct object that foo points to has been properly initialized.
(`foo->method()` doesn't work because there's no implicit `this`
as there is in C++.)
Yes, it could be done that way, but probably wouldn't be, because
the space costs are too high: a pointer-to-function would be
needed in every object for every method. Adding one level of
indirection allows only one pointer for each struct, plus a
single instance of a struct with method function pointers, eg,
foo->m->method( foo )
Even though this way is better than the previous one, neither is
really acceptable for large-scale deployment. The reason is
having to duplicate the object being sent to (ie, in the sense of
having to name it twice, eg, the token 'foo' in this case).
In terms of usability having the syntactic sugar for method
calls is pretty much an essential component here.
The technique described by Keith is widely used in kernels,
particularity in module and driver interfaces. The space overhead is
a cost worth bearing when you want a modular interface and there
aren't too many objects. [...]
Yes, I didn't mean to imply it's unworkable in all
contexts. If the number pointers is small, or the
number of objects is small, or especially if both
are small, then it's not too bad. But it doesn't
scale to the general case of lots of pointers and
lots of objects, which is (at least potentially)
true in the case that was under discussion.
XView scaled pretty well :)
If you mean it kept working reasonably in all the particular
systems you experienced, then at least one of the relevant
dimensions must have been under-represented. The mathematics is
not wrong.
Ian Collins
2017-05-26 23:06:35 UTC
Permalink
Raw Message
Post by Tim Rentsch
Post by Ian Collins
Post by Tim Rentsch
And we can do `foo->method(foo)` in C. It just requires `method` to
be a member of pointer-to-function type, and some way to ensure that
the struct object that foo points to has been properly initialized.
(`foo->method()` doesn't work because there's no implicit `this`
as there is in C++.)
Yes, it could be done that way, but probably wouldn't be, because
the space costs are too high: a pointer-to-function would be
needed in every object for every method. Adding one level of
indirection allows only one pointer for each struct, plus a
single instance of a struct with method function pointers, eg,
foo->m->method( foo )
Even though this way is better than the previous one, neither is
really acceptable for large-scale deployment. The reason is
having to duplicate the object being sent to (ie, in the sense of
having to name it twice, eg, the token 'foo' in this case).
In terms of usability having the syntactic sugar for method
calls is pretty much an essential component here.
The technique described by Keith is widely used in kernels,
particularity in module and driver interfaces. The space overhead is
a cost worth bearing when you want a modular interface and there
aren't too many objects. [...]
Yes, I didn't mean to imply it's unworkable in all
contexts. If the number pointers is small, or the
number of objects is small, or especially if both
are small, then it's not too bad. But it doesn't
scale to the general case of lots of pointers and
lots of objects, which is (at least potentially)
true in the case that was under discussion.
XView scaled pretty well :)
If you mean it kept working reasonably in all the particular
systems you experienced, then at least one of the relevant
dimensions must have been under-represented. The mathematics is
not wrong.
Well the data set was every SunOS desktop until Solaris moved to Motif
in the mid 90s! Maybe we didn't have such cluttered desktops back then,
but what we had was impressive for the hardware it ran on.
--
Ian
Tim Rentsch
2017-05-20 02:07:54 UTC
Permalink
Raw Message
but that seems so close to the original as no matter.
I tend to scope as narrowly as possible, and now when I see
if() {
// some stuff;
} else {
int whatever, variables;
char * we, need;
// other stuff
}
my mind has been trained to say "Should that block with the all
those extra variables actually be a subroutine"?
I often have a similar reaction but for somewhat different
reasons. My heuristic compares the relative level of detail in
the two branches (or that of the branches and the rest of the
function.
In addition to detail (I tend to see it a balance), I also like to add
a degree of narrative to the heuristic. If extracting the function
makes the narrative of this function easier to follow, do it.
For example given
if (some condition)
{
// bunch of code to process condition A
...
}
else
{
// bunch of code to process condition B
...
}
I prefer to see
if (some condition)
{
processConditionA();
}
else
{
processConditionB();
}
So I'm not distracted by the processing code. If I want to read it, I
know where I can see it. If I don't, it doesn't get in the way of
reading the current function.
I think what you're talking about is a little bit different than
what I'm talking about. Now I'm guessing what you mean by "make
the narrative better", but if I have understood you then what I
have is a heuristic for /predicting/ whether the narrative will get
better. If I knew the narrative would get better then I wouldn't
need the heuristic! So I'm not sure if I have misunderstood you or
if we're talking about different things, or what.

In the example you give (re-written as pseudo-code):

IF relevant test
.. glob of code when test is TRUE
ELSE
.. glob of code when test is FALSE
FI

and assuming that some revising looks called for, whether I would
choose to subroutine-ize one branch or both branches would be
influenced a lot by what's in the globs. I don't have a general
rule like subroutine-ize neither or both.

(Let me be clear that I really don't know if this is what you
were trying to say; I'm not trying to put words in your mouth,
just describe my own reactions.)
Ian Collins
2017-05-20 10:04:55 UTC
Permalink
Raw Message
Post by Tim Rentsch
I often have a similar reaction but for somewhat different
reasons. My heuristic compares the relative level of detail in
the two branches (or that of the branches and the rest of the
function.
In addition to detail (I tend to see it a balance), I also like to add
a degree of narrative to the heuristic. If extracting the function
makes the narrative of this function easier to follow, do it.
For example given
if (some condition)
{
// bunch of code to process condition A
...
}
else
{
// bunch of code to process condition B
...
}
I prefer to see
if (some condition)
{
processConditionA();
}
else
{
processConditionB();
}
So I'm not distracted by the processing code. If I want to read it, I
know where I can see it. If I don't, it doesn't get in the way of
reading the current function.
I think what you're talking about is a little bit different than
what I'm talking about. Now I'm guessing what you mean by "make
the narrative better", but if I have understood you then what I
have is a heuristic for /predicting/ whether the narrative will get
better. If I knew the narrative would get better then I wouldn't
need the heuristic! So I'm not sure if I have misunderstood you or
if we're talking about different things, or what.
IF relevant test
.. glob of code when test is TRUE
ELSE
.. glob of code when test is FALSE
FI
and assuming that some revising looks called for, whether I would
choose to subroutine-ize one branch or both branches would be
influenced a lot by what's in the globs. I don't have a general
rule like subroutine-ize neither or both.
(Let me be clear that I really don't know if this is what you
were trying to say; I'm not trying to put words in your mouth,
just describe my own reactions.)
I think you have the gist of what I was saying. I often write my code
starting with the higher level steps of function, so I often end up with
functions for even short globs. So I would start with the function
calls rather than than extracting them later.
--
Ian
Richard Bos
2017-05-21 17:30:19 UTC
Permalink
Raw Message
Agree with all of this. ... What would be required to reduce the discrepancy?
I expect there would be a market for a 3rd edition, probably with a new co-author,
that covers C99 material and sneaks a few footnotes in to fill up the glosses.
Is seebs available? kickstarter??
Perhaps what's needed is an official recognized language which stops trying
to undefine things that were defined in K&R2.
Perhaps what's needed is for you to get your head around the fact that
your take on what K&R means is not the definitive interpretation.

Richard
Richard Bos
2017-05-21 17:34:47 UTC
Permalink
Raw Message
Post by Richard Heathfield
<snip>
1. Have no separator. That avoids any work at all, and has no
conflicts, but gives no advantages.
2. Have underscores as a separator. That makes things as nice as
possible for C, but introduces an inconsistency between the languages.
This is a big disadvantage in the eyes of many - especially, perhaps,
embedded programmers who regularly combine C and C++ code in the same
program and who are likely to be amongst the heaviest users of the
feature (especially for binary literals).
Personally, I think that the fact that it makes it look like an
identifier _is_ a strike against this option. I don't like it.
Post by Richard Heathfield
3. Have a single quote for a separator. That keeps consistency with
C++, but does so by making C's choice as ugly as that of C++.
Ugly is in the eye of the beholder - I prefer this option. Purely on
aesthetic grounds.
Post by Richard Heathfield
4. Allow both as separators in C. Developers can choose to write nice C
code, or C code that is consistent with C++. But that also means they
can write ugly C code, and C code that is /inconsistent/ with C++.
Ew, no.
Post by Richard Heathfield
5. Use the comma, as God intended.
God is English? New to me.
Post by Richard Heathfield
I know what you're thinking. What about the existing comma operator and
separator?
Well, any sensible person puts at least one whitespace character after
such commas, so we need only insist that the digit separator is preceded
Significant whitespace is a tool of the devil. Sorry, of Python. Nope, I
was right the first time 'round. Significant whitespace must be kept to
a minimum. This, therefore, is not a reasonable option.

Richard
Richard Heathfield
2017-05-21 18:40:51 UTC
Permalink
Raw Message
Post by Richard Bos
Post by Richard Heathfield
<snip>
1. Have no separator. That avoids any work at all, and has no
conflicts, but gives no advantages.
2. Have underscores as a separator. That makes things as nice as
possible for C, but introduces an inconsistency between the languages.
This is a big disadvantage in the eyes of many - especially, perhaps,
embedded programmers who regularly combine C and C++ code in the same
program and who are likely to be amongst the heaviest users of the
feature (especially for binary literals).
Personally, I think that the fact that it makes it look like an
identifier _is_ a strike against this option. I don't like it.
Post by Richard Heathfield
3. Have a single quote for a separator. That keeps consistency with
C++, but does so by making C's choice as ugly as that of C++.
Ugly is in the eye of the beholder - I prefer this option. Purely on
aesthetic grounds.
Post by Richard Heathfield
4. Allow both as separators in C. Developers can choose to write nice C
code, or C code that is consistent with C++. But that also means they
can write ugly C code, and C code that is /inconsistent/ with C++.
Ew, no.
None of those were my idea. I just quoted them.
Post by Richard Bos
Post by Richard Heathfield
5. Use the comma, as God intended.
God is English? New to me.
Yeah. He lives in Gloucestershire, and has done since at least the
Middle Ages. (A search for <proverb God Gloucestershire> may prove
enlightening.)
Post by Richard Bos
Post by Richard Heathfield
I know what you're thinking. What about the existing comma operator and
separator?
Well, any sensible person puts at least one whitespace character after
such commas, so we need only insist that the digit separator is preceded
Significant whitespace is a tool of the devil. Sorry, of Python. Nope, I
was right the first time 'round. Significant whitespace must be kept to
a minimum. This, therefore, is not a reasonable option.
Well, I didn't claim that it's a /reasonable/ proposition. It would get
my vote, though; and I should perhaps warn you that, if we have a
referendum on the issue, I'm currently on a roll.
--
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-05-22 08:13:39 UTC
Permalink
Raw Message
Post by Richard Heathfield
Post by Richard Bos
Post by Richard Heathfield
<snip>
1. Have no separator. That avoids any work at all, and has no
conflicts, but gives no advantages.
2. Have underscores as a separator. That makes things as nice as
possible for C, but introduces an inconsistency between the languages.
This is a big disadvantage in the eyes of many - especially, perhaps,
embedded programmers who regularly combine C and C++ code in the same
program and who are likely to be amongst the heaviest users of the
feature (especially for binary literals).
Personally, I think that the fact that it makes it look like an
identifier _is_ a strike against this option. I don't like it.
Post by Richard Heathfield
3. Have a single quote for a separator. That keeps consistency with
C++, but does so by making C's choice as ugly as that of C++.
Ugly is in the eye of the beholder - I prefer this option. Purely on
aesthetic grounds.
Post by Richard Heathfield
4. Allow both as separators in C. Developers can choose to write nice C
code, or C code that is consistent with C++. But that also means they
can write ugly C code, and C code that is /inconsistent/ with C++.
Ew, no.
None of those were my idea. I just quoted them.
Post by Richard Bos
Post by Richard Heathfield
5. Use the comma, as God intended.
God is English? New to me.
Yeah. He lives in Gloucestershire, and has done since at least the
Middle Ages. (A search for <proverb God Gloucestershire> may prove
enlightening.)
Well, Jesus is Scottish - from the Gallowgate in Glasgow, to be more
specific.
Richard Bos
2017-05-21 18:05:32 UTC
Permalink
Raw Message
Post by Richard Heathfield
<snip>
Here's a pattern consisting of 1 one, 2 zeros, 3 ones, 4 zeros etc
0b'111111111'00000000'1111111'000000'11111'0000'111'00'1
In hex it might be 0x'1FF0'0FE0'7C39; not quite so easy to verify!
That's not quite the right way to put it because a hex constant like
that would deserve a comment, and the comment can make it very simple to
// 000111111111000000001111111000000111110000111001
// \__/\__/\__/\__/\__/\__/\__/\__/\__/\__/\__/\__/
// 1 F F 0 0 F E 0 7 C 3 9
It's harder to generate, but one you've done that, it's not hard to make
it clear to the reader.
And this is, of course, _much_ more concise than using a binary
constant...

Richard
Tim Rentsch
2017-05-23 16:42:40 UTC
Permalink
Raw Message
On Tue, 16 May 2017 12:54:37 -0700, Tim Rentsch
C has the wonderful property that everything is out in the open,
with nothing hidden away somewhere else in the program. Consider
while ( c = *p++ ) {
Foo x = { p-1, c };
if ( c == '\n' ) continue;
... rest of loop ...
}
In C there is no need to wonder if moving the declaration of 'x'
to just after the if() will change the program behavior. But if
ctors and dtors have been added that is no longer true. To
understand the behavior of this while() loop it is necessary to
look somewhere else besides just the code in the loop. What is
more important, that dependency is not obvious in the code itself
(unlike, eg, function calls). That difference would give the
language a very different feel.
That non-locality is certainly one of the complaints commonly leveled
against C++. As a practical matter I've never had real trouble with
that, outside of a few excessively large class hierarchies
(fortunately the class hierarchy needing a wall-sized chart to diagram
has fallen somewhat out of fashion). In any event, a limited
implementations of classes (without inheritance) would make for a
pretty short search for the code being invoked).
It would, assuming the source is available. If the code is
distributed as a library, and only the headers as source,
the actual function definitions would not be available. So
that's something to think about.
[...]
But a bigger problem is local
objects. A call to atexit() affects global program behavior but
it doesn't affect local program behavior - break's and continue's
are stil just break's and continue's, etc. With local objects
(that have constructor/destructor behavior) that is no longer
true - any control transfer might be accompanied by a dtor call.
Again, it's not something I feel is a problem. [...]
I would say it isn't a problem, /if/ the uses are reasonably
restrained in their expectations. A lot of machinery has found
its way into C++ as a result of wanting to use ctors and dtors to
do resource management. Presumably you don't want all of that
machinery in your limited classes extension. Given that, it
would be good to think about what functionality you believe /is/
really needed, and design the relevant language features to
provide that but not provide more than they have to. Who was it
who said this - the most important decisions in language design
are not what features to put in but which ones to leave out. (My
guess is Tony Hoare but I'm not sure.) At some level the point
I'm trying to make is think about what you want not only in terms
of what you want to include but also what capabilities are
specifically excluded. You have already done that to some extent
(inheritance, exceptions, I don't remember what else). I believe
it's important to do that also specifically in the area of ctors
and dtors.
s***@casperkitty.com
2017-05-23 17:44:30 UTC
Permalink
Raw Message
Post by Tim Rentsch
I would say it isn't a problem, /if/ the uses are reasonably
restrained in their expectations. A lot of machinery has found
its way into C++ as a result of wanting to use ctors and dtors to
do resource management. Presumably you don't want all of that
machinery in your limited classes extension. Given that, it
would be good to think about what functionality you believe /is/
really needed, and design the relevant language features to
provide that but not provide more than they have to. Who was it
who said this - the most important decisions in language design
are not what features to put in but which ones to leave out. (My
guess is Tony Hoare but I'm not sure.) At some level the point
I'm trying to make is think about what you want not only in terms
of what you want to include but also what capabilities are
specifically excluded. You have already done that to some extent
(inheritance, exceptions, I don't remember what else). I believe
it's important to do that also specifically in the area of ctors
and dtors.
Exceptions are simultaneously one of the most useful features of C++ and
the most problematic. A fundamental problem is that there are a variety
of ways that exception cleanup can be handled, and if more than one is used
within a program they may interact badly. Because of this, any language
which supports exceptions will need to impose severe restrictions upon the
ways in which its code can interact with code written in other languages
that handle exceptions differently.

It may be helpful to have a language offer some assistance with regard to
exceptions and cleanup, but an implementation suitable for producing code
that will interact smoothly with other languages must allow a programmer
control of the mechanisms behind any such assistance. For example, an
implementation might call a user-supplied function whenever code enters
a catch block or a region requiring cleanup, exits a cleanup region or
catch block, or throws an exception; user code that needs to interact with
code using some a particular exception mechanism could include a function
to implement the behaviors the compiler needs, using the other program's
mechanisms.
Tim Rentsch
2017-05-23 16:45:50 UTC
Permalink
Raw Message
C++ classes provide an excellent intermediate scope for items. If
there were a major feature of C++ I'd want in C, it would be classes
(not inherence, overloading, templates...).
I'm curious to know what sort of items you envision putting in
those classes. For all the examples I have thought of it seems
of little practical value or there is a straightforward way of
accomplishing the same goal without needing any extensions. What
are your use cases?
Lightweight classes can just be structs, but with the ability to
* Typedefs
* Enums
* Structs (in a form that can be used from outside)
* Variables [static, to distinguish from normal members]
* Functions
* Nested classes
They would automatically provide a namespace mechanism. [...]
Apparently this is mostly not what Robert Wessel was
interested in, so I have nothing further to say here
beyond what may come up in those other followups.
bartc
2017-05-23 17:07:41 UTC
Permalink
Raw Message
Post by Tim Rentsch
I'm curious to know what sort of items you envision putting in
those classes. For all the examples I have thought of it seems
of little practical value or there is a straightforward way of
accomplishing the same goal without needing any extensions. What
are your use cases?
Lightweight classes can just be structs, but with the ability to
* Typedefs
* Enums
* Structs (in a form that can be used from outside)
* Variables [static, to distinguish from normal members]
* Functions
* Nested classes
They would automatically provide a namespace mechanism. [...]
Apparently this is mostly not what Robert Wessel was
interested in, so I have nothing further to say here
beyond what may come up in those other followups.
A major issue for large projects is namespace pollution. The ability
to bury most bits of a particular subsystem inside a class greatly
reduces that problem (leaving only the name of the class globally
visible). The ability to make items private further enhances
encapsulation.
Obviously you can put data items inside a struct in C, but not (in any
"good" way), functions (methods) intended to work on them.
Which along the same lines of what I said.
--
bartc
Tim Rentsch
2017-05-26 22:41:53 UTC
Permalink
Raw Message
Post by bartc
Post by Tim Rentsch
I'm curious to know what sort of items you envision putting in
those classes. For all the examples I have thought of it seems
of little practical value or there is a straightforward way of
accomplishing the same goal without needing any extensions. What
are your use cases?
Lightweight classes can just be structs, but with the ability to
* Typedefs
* Enums
* Structs (in a form that can be used from outside)
* Variables [static, to distinguish from normal members]
* Functions
* Nested classes
They would automatically provide a namespace mechanism. [...]
Apparently this is mostly not what Robert Wessel was
interested in, so I have nothing further to say here
beyond what may come up in those other followups.
A major issue for large projects is namespace pollution. The ability
to bury most bits of a particular subsystem inside a class greatly
reduces that problem (leaving only the name of the class globally
visible). The ability to make items private further enhances
encapsulation.
Obviously you can put data items inside a struct in C, but not (in any
"good" way), functions (methods) intended to work on them.
Which along the same lines of what I said.
You missed my point.
Tim Rentsch
2017-05-24 14:55:56 UTC
Permalink
Raw Message
Each version of the C standard since C90 has added features that
have no counterpart in C++, but later versions of C++ have often
added some of those same features. The ones that haven't yet been
added to C++, are relatively few, and not yet widely used. The
biggest exceptions are things like designated intializers and
compound literals, but those are merely convenience features for
which there exists less convenient ways to write the same thing
that do have the same meaning in C and in C++.
An interesting phrase - "merely convenience features". Let's
look at some of the language features in C for which there
exists less convenient ways to write the same thing:

for(), while(), switch(), and do/while() statements
'else' clauses
initializers on local variables
printf(), fprintf(), etc, functions
variadic functions
function prototypes
&&, ||, ?: operators (and some others I'm sure)
typedef's
mixed declarations and statements
compound statements

The whole point of language constructs is to make programs and
programming more convenient: easier to write, easier to read,
easier to understand, easier to get right, and easier to maintain
and extend. Saying there exist less convenient ways to write
the same thing is saying virtually nothing.
Tim Rentsch
2017-05-24 15:14:03 UTC
Permalink
Raw Message
Taking the comma operator out of C is a non-starter, for
obvious reasons. There really isn't any serious debate on
the question; IME people who disdain the comma operator do
so mainly out of historical prejudice, which isn't enough
to lead to any conversation of substance.
From a language-design perspective, the only advantage I can see to
sequentially evaluating subexpressions using a comma operator, rather
than a gcc-style statement expression, is that on a single-pass compiler
the comma operator would naturally leave the execution stack in the
correct state, while statement expressions would sometimes require some
slight gymnastics with the stack.
Yes, that could explain why more people don't seek your
advice in discussions on language design.
Richard Bos
2017-05-24 20:49:36 UTC
Permalink
Raw Message
C++ 17, has now a new if.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r0.html
if ( for-init-statement condition ) statement
Would you like to see this if on C?
Yuck. No. We don't need it, and what we do need is a smaller, not a
larger, language.
So what would you remove from C?
Hard one; you can't really remove things from C without angering people
who use it, even if it's only in legacy code. But just for me, well,
quite a lot of the additions since 1989, I don't really need. I do
realise that this is not a universally popular stance.

Anything to do with _me_ telling the implementation what to optimise,
for one, I would ditch. That's its job, not mine. Things like register
and inline deserve to go the way of the dodo, at the very least. But
also atomics... well, yes, you do need them with threads, but aren't
threads yesterday's solution to today's problem for tomorrow's programs,
anyway? _Noreturn... come on! Why?

Trigraphs. Digraphs solve that problem better. Yes, all of it. No,
including the exception you're thinking of now. If you can't print it,
you shouldn't want to #include (sorry, ??=include) it. F??=ck trigraphs.

Either extended or multi-byte characters. You pick. But make it
consistent, and use _one_ system. (Of course, a native Unicode system
should have chars of more than eight bits, and there should be separate
types for char and byte. But that's another fight, which we've already
lost.)

Quite a few cases of superfluous duplication, and historical functions
tied to a specific use. <time.h> is a prime example. Why do we have all
those functions? Get rid of most, reduce them to the ones you need to
get it right. ctime()? Bog off. We want machine time <-> time struct <->
time string, plus one (one!) function to set our time zone, and that's
it. Similar cases can be found in other headers; ato*() are an obvious
example.

<iso646.h>. I really shouldn't have to explain that one.

I am quite aware that other people would make other decisions. However,
that's somewhat beside my point, which is this: parsimony, rather than
gluttony, should be our guiding principle. C++ should go the other way
'round. That way, and only that way, there is a reason for both
languages to exist.

Richard
s***@casperkitty.com
2017-05-24 21:11:37 UTC
Permalink
Raw Message
Post by Richard Bos
Anything to do with _me_ telling the implementation what to optimise,
for one, I would ditch. That's its job, not mine. Things like register
and inline deserve to go the way of the dodo, at the very least. But
also atomics... well, yes, you do need them with threads, but aren't
threads yesterday's solution to today's problem for tomorrow's programs,
anyway? _Noreturn... come on! Why?
I would argue the opposite, though I would suggest that directives should
be focused on letting the compiler know things that it may safely assume.

Under C89, compilers which received a pointer to a structure had to
recognize aliasing between that structure and any other structure which
shared a common initial sequence. Some programs need to use CIS aliasing
and some don't. Having directives to indicate when such aliasing is or
is not needed would let compilers optimize the cases where it isn't, while
avoiding breaking any code where it is. I'd regard such an approach as
far less harmful than simply declaring that code which had defined behavior
on C89 should have Undefined Behavior under C99 without any way of expressing
the same semantics.
Richard Bos
2017-05-24 21:37:15 UTC
Permalink
Raw Message
Post by s***@casperkitty.com
Post by Richard Bos
Anything to do with _me_ telling the implementation what to optimise,
for one, I would ditch. That's its job, not mine. Things like register
and inline deserve to go the way of the dodo, at the very least. But
also atomics... well, yes, you do need them with threads, but aren't
threads yesterday's solution to today's problem for tomorrow's programs,
anyway? _Noreturn... come on! Why?
I would argue the opposite, though I would suggest that directives should
be focused on letting the compiler know things that it may safely assume.
Yes, but you're deranged.

Richard
Tim Rentsch
2017-05-26 22:52:02 UTC
Permalink
Raw Message
C++ 17, has now a new if.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r0.html
if ( for-init-statement condition ) statement
Would you like to see this if on C?
Yuck. No. We don't need it, and what we do need is a smaller, not a
larger, language.
So what would you remove from C?
Hard one; you can't really remove things from C without angering
people who use it, even if it's only in legacy code. But just for
me, well, quite a lot of the additions since 1989, I don't really
need. I do realise that this is not a universally popular stance.
Anything to do with _me_ telling the implementation what to
optimise, for one, I would ditch. That's its job, not mine.
Things like register and inline deserve to go the way of the dodo,
at the very least. [...]
Note that 'inline' has semantic implications beyond just hinting
that the function be expanded inline. It may be that you still
want to get rid of it, but before making that call you might want
to check out what those extra semantics are.
Richard Bos
2017-06-03 10:20:46 UTC
Permalink
Raw Message
Post by Tim Rentsch
Post by Richard Bos
So what would you remove from C?
Anything to do with _me_ telling the implementation what to
optimise, for one, I would ditch. That's its job, not mine.
Things like register and inline deserve to go the way of the dodo,
at the very least. [...]
Note that 'inline' has semantic implications beyond just hinting
that the function be expanded inline.
Nevertheless, they all serve the aim of optimisation.

Richard
Tim Rentsch
2017-06-05 20:50:43 UTC
Permalink
Raw Message
Post by Richard Bos
Post by Tim Rentsch
Post by Richard Bos
So what would you remove from C?
Anything to do with _me_ telling the implementation what to
optimise, for one, I would ditch. That's its job, not mine.
Things like register and inline deserve to go the way of the dodo,
at the very least. [...]
Note that 'inline' has semantic implications beyond just hinting
that the function be expanded inline.
Nevertheless, they all serve the aim of optimisation.
That seems like an odd comment. Because there are also semantic
differences, adding or removing 'inline' can change how the
program works. Would also eliminate 'restrict'? It serves the
aim of optimization: it's never wrong to take out a 'restrict',
it only might result in a slower program. This makes 'inline'
and 'restrict' different from 'register', which for all practical
purposes has no semantic implications (the only one being that &
cannot be used on a 'register' variable).

Loading...