Differences between C and C++

C

Chris H

Ian Collins <ian- said:
We didn't have one (this was early 90s), the C++ compiler was our
static analyser!

Static analysers for C have been around since the late 1970's
There were some very good C90 static analysers from about 1991.

BTW had C++ even been standardised at that time?
A compiler translates code. It is NOT a static analyser.

So what was your excuse again?
 
I

Ian Collins

Static analysers for C have been around since the late 1970's
There were some very good C90 static analysers from about 1991.

Well we didn't have one.
BTW had C++ even been standardised at that time?
No.

A compiler translates code. It is NOT a static analyser.

So what was your excuse again?

Well a huge number of bugs in that embedded code base were caused by
wrong parameter types (mostly enums) being passed around. The C++
compiler was an ideal tool to flush those out. Once those were removed,
our lives were much more enjoyable. Then there was the testing I was
able to do once I could run the code on my desktop...
 
M

Malcolm McLean

Well a huge number of bugs in that embedded code base were caused by
wrong parameter types (mostly enums) being passed around.  The C++
compiler was an ideal tool to flush those out.  Once those were removed,
our lives were much more enjoyable.  Then there was the testing I was
able to do once I could run the code on my desktop...
enums should have their own namespace, as well

enum states { ALABAMA ... WASHINGTON ...};
enum presidents {WASHINGTON ... OBAMA };

is an unexceptional thing to want to do.
 
B

Ben Bacarisse

BGB said:
they are functionally equivalent in that both can be called without
any arguments and will exhibit the same behavior.

there will be a difference at which point one calls the function with
arguments (in one case the compiler will reject the code making the
call and in the other it wont). however, if one assumes that the code
is in-error (by calling a function which doesn't take arguments with
an arguments list), then this doesn't result in a behavioral
difference in the code to be run.

I think that providing a prototype for error checking calls is important
to the extent that () and (void) are not functionally equivalent, but
you may define the term how you like. The difference in the semantics
is important, the term used to describe the similarities is not.
yes, it does do this, but this is not much different than passing or
not passing "-Werror-implicit-function-declaration" to GCC.

How could it be similar? The implicit-function-declaration warning (or
error) applies only to code where there is neither a declaration nor a
definition in scope, i.e. where neither the () or the (void) form has
been used (yet).

BTW, a C99 compiler *must* complain about the use of a function with no
declaration, but since both the () and (void) forms constitute a
declaration that has no bearing on this issue.

<snip>
 
K

Keith Thompson

Malcolm McLean said:
enums should have their own namespace, as well

enum states { ALABAMA ... WASHINGTON ...};
enum presidents {WASHINGTON ... OBAMA };

is an unexceptional thing to want to do.

And I've worked extensively in at least one language where they do, but
I don't think such a change could be made to C without breaking tons of
existing code.
 
B

Ben Bacarisse

Malcolm McLean said:
enums should have their own namespace, as well

enum states { ALABAMA ... WASHINGTON ...};
enum presidents {WASHINGTON ... OBAMA };

is an unexceptional thing to want to do.

That's only the start. How would one determine which WASHINGTON is
intended? It's easy in languages like ML with type inference (or in
strongly typed languages like Ada) but it's problematic in C.
 
I

Ian Collins

enums should have their own namespace, as well

enum states { ALABAMA ... WASHINGTON ...};
enum presidents {WASHINGTON ... OBAMA };

is an unexceptional thing to want to do.

Easy in C++:

namespace states { enum { ALABAMA, WASHINGTON }; }
namespace presidents { enum{ WASHINGTON, OBAMA }; }

namespace would be a useful addition to C.

However, the main problem I faced was values not in the enum being passed.
 
M

Malcolm McLean

That's only the start.  How would one determine which WASHINGTON is
intended?  It's easy in languages like ML with type inference (or in
strongly typed languages like Ada) but it's problematic in C.
If you assign an enum to a variable of another type or use it in an
expression you'd have to cast it.

int fortran_state_index = (state) WASHINGTON + 1;

However this should be relatively rare.
 
B

BGB

So you really are banking up problems!

One of the original reasons I put a C project through a C++ compiler was
to spot the (considerable number) of functions being called with the
wrong parameters.

I really really wish K&R style prototypes were removed completely from C.

no, the "()" lines are mostly non-argument functions, just the tendency
is to use "()" rather than "(void)", mostly for aesthetic reasons.

but, in all, only a small percentage of the functions around are
no-argument functions (as in, lack a parameter list), hence, no big issue...

459 out of 15281 prototypes is hardly a large number...
 
B

BGB

I think that providing a prototype for error checking calls is important
to the extent that () and (void) are not functionally equivalent, but
you may define the term how you like. The difference in the semantics
is important, the term used to describe the similarities is not.

well, it is agreed that the semantics are different...

however, in the vast majority of cases, the observable behavior (both
what happens when well-formed code is compiled, and also when the code
is run), is equivalent.


it is generally fairly well established that things can be substituted
for things which are internally or formally very different without
effecting the observable behavior, and are hence interchangeable.

"if it looks like a duck and quacks like a duck, it is a duck".


provided one calls the function as:
"foo();" then whether it is declared with "()" or "(void)" doesn't make
a whole lot of difference, and the case where one calls "foo(bar);" can
be ignored as it falls outside of the established scope (calling a
no-argument function).


it is really no different than misusing a "void *" pointer. one can do
it, and the compiler wont complain, but it is the programmers' problem
is the behavior is not what was expected, rather than that the compiler
didn't give a complaint about it.

and if one wants the compiler to complain, they can typedef and replace
the "void *" pointer with, say:
"typedef dummy_foo_s *foo_ptr;"

"foo_ptr whatever;"

and, apart from giving a few warnings about implicit conversions to/from
the type, the external behavior of the program is equivalent either way.

How could it be similar? The implicit-function-declaration warning (or
error) applies only to code where there is neither a declaration nor a
definition in scope, i.e. where neither the () or the (void) form has
been used (yet).

BTW, a C99 compiler *must* complain about the use of a function with no
declaration, but since both the () and (void) forms constitute a
declaration that has no bearing on this issue.

GCC requires a commandline option to complain, and MSVC doesn't complain
at all...

"(void)" makes the compiler complain, but "()" doesn't require the
compiler to complain.

seems similar enough...
 
B

BGB

it is generally fairly well established that things can be substituted
for things which are internally or formally very different without
effecting the observable behavior, and are hence interchangeable.

"if it looks like a duck and quacks like a duck, it is a duck".

or, stated another way:
it there exists some context A by which X and Y may be used
interchangeably, then within this context X and Y are equivalent.

provided one calls the function as:
"foo();" then whether it is declared with "()" or "(void)" doesn't make
a whole lot of difference, and the case where one calls "foo(bar);" can
be ignored as it falls outside of the established scope (calling a
no-argument function).

and in the common usage case, "()" and "(void)" are equivalent by the
above axiom.

where the context in this case is "function is only ever called with an
empty argument list", for which is the presumed behavior when the
function doesn't take any arguments (after all, it there were arguments,
presumably someone would have put them in the arguments list, hence
their absence implies that no arguments are to be given to the function
when it is called).


this doesn't mean to imply that there is universal equivalence between X
and Y...

it is just like how, for example, Vaseline, Crisco, and Lard (or Butter
and Margarine, or WD40) are not the same in all cases, but there are a
range of tasks in which they may be used interchangably (given they
exhibit essentially equivalent behavior, and may be substituted
as-needed for a given set of tasks, or as based on personal preference).


much like one can often use a screwdriver as an all-purpuse multi-tool:
as a tack hammer;
as a prying or scraping implement;
for carving holes in things (wood, plastic, ...);
....

or in combinations thereof (say, one screwdriver as a wedge and another
as a hammer...).

whether or not "ideal" (or may wear or damage the tools), one can't deny
that it will often work and will get the job done.


or such...
 
M

Malcolm McLean

That does not answer the question I asked.  Which WASHINGTON is this?
You had only two (with different values) so I suppose that the cast
could be taken by the compiler to mean that the "other" WASHINGTON was
intended but that's a peculiar rule and does not scale.  If there are
three enums that define WASHINGTON, which one gets converted here?
No, I mean the opposite.
In C++ we would write state::WASHINGTON. (state) WASHINGTON is more C-
like,
because

state BillGateseshangout = WASHINGTON;
and
president thatchapwiththefunnywig = WASHINGTON;

are both fine.
However this should be relatively rare.

Unless you change lots of other rules I suspect it will be quite
common.  Consider 'area[WASHINGTON]' for example.  You'd have to
re-define the integer promotions and where they occur.
That's a nuisance, yes. Ideally we'd like to be able to define "area"
as an enum-states indexed array. But that's too big a change to the
language.
 
M

Malcolm McLean

That's giving a unique namespace to *every* enum. and strongly typing
any variables holding them.  It's a *really* big change to the language..
Not really. Struct members also have their own namespace, and structs
are strongly typed.

The problem is getting the syntax right.
 
K

Keith Thompson

Malcolm McLean said:
If you assign an enum to a variable of another type or use it in an
expression you'd have to cast it.

int fortran_state_index = (state) WASHINGTON + 1;

However this should be relatively rare.

That almost reverses the meaning of a cast. Normally a cast takes
an expression of some type and converts it to a different type.
(I know the type doesn't *have* to be different, but it normally is.)
Here you're using a cast to assert that the expression is *already*
of the specified type.

(Ada has conversions, which are like C casts, and qualified
expressions, which are something like what you're suggesting.)

And how would you convert the constant ADAMS (a president) to type
state? If (state) ADAMS is legal, then surely (state) WASHINGTON
is ambiguous.

If you're seriously suggesting that this feature be added in a new
version of C, it's vanishingly unlikely to be accepted because it
would break so much existing code. If you're merely speculating
about a feature that C *could have* had, that could be interesting.
 
K

Keith Thompson

BGB said:
no, the "()" lines are mostly non-argument functions, just the tendency
is to use "()" rather than "(void)", mostly for aesthetic reasons.

Are aesthetics more important than correctness?

The way to say that a C function takes no arguments is "(void)".
"()" means something very different, no matter how pretty it looks.
but, in all, only a small percentage of the functions around are
no-argument functions (as in, lack a parameter list), hence, no big issue...

459 out of 15281 prototypes is hardly a large number...

459 is a pretty large number whatever the total might be. If you
accidentally pass an argument to any of those 459 functions, the
compiler won't tell you about your error. Is it really worth it
to avoid having that ugly "void" in each signature? I certainly
don't think so.
 
K

Keith Thompson

Malcolm McLean said:
Not really.

Yes, really.
Struct members also have their own namespace, and structs
are strongly typed.

The name of a struct member is never used except in a context
that unambiguously determines the enclosing struct.

Enum constants not contained in anything; they're used by themselves.
In C as it's currently defined, they're nothing more than constants of
type int. Even in C++, they're just constants of the enumeration type.
The problem is getting the syntax right.

Ok, here's a way to do what you propose without (I think) breaking
existing code.

Introduce a new token "::". The prefix is an enum type (possibly
a typedef). The suffix is an enumeration constant.

Any enumeration constant that's defined only once (such as ALABAMA)
can be referred to either without qualification, or with the type
specified: "enum state::ALABAMA". A constant that's defined more
than once (such as WASHINGTON) must be qualified.

I don't see that this really buys us much. We could accomplish the
same thing in current C by changing the name of one of the constants.
On the other hand, it could be helpful if the types are defined
in independent libraries -- but it only addresses conflicting
enumeration constants, not any of the other possible conflicting
identifiers.

A C++-like namespace facility would potentially solve the same
problem and a number of others as well.
 
B

BGB

I've addressed this in your followup.


So you are defining the scope of this discussion to be only correct
programs? That discussion does not interest me. You can use ()
everywhere in correct programs and everything will work. Presumably you
use prototypes in other cases but you seem to have decided that the
checks you otherwise like to have don't matter for no-argument
functions. I don't see why.

prototypes offer a unique power:
they make float and double arguments and return values work correctly;
they make pointer arguments and return values work correctly on some
systems (such as x86-64);
....

for example, if no prototype is used:
the compiler will automatically promote any float arguments to double;
any return value will be assumed to be "int", regardless of whatever is
there, which leads to bad results if either the argument if a float or
double (and thus goes into a different CPU register or similar), or
"sizeof(void *)!=sizeof(int)", or if a different CPU register is used
(say, for int and pointer returns).

so, the prototypes are often needed for the correct behavior of the program.


the result then is that a missing prototype may cause the program to
misbehave or crash, which itself was enough to win me over to the
"function prototypes are needed" side of things, albeit I am lazy and
usually use tools to write out the headers with the prototypes by
looking at the source.


I have determined that actually, this automatic prototype handling seems
more reliable than the traditional way of manually doing it.

for example, at one point I switched over some code which used the more
traditional means to using auto-generated headers, and found several
occurrences where prototypes were declared elsewhere, but with the wrong
number or types of arguments, and where the code in that section would
call the function with the wrong arguments. the result of this was
fixing up some of these calls (and commenting out the ill-informed
prototypes).

Except that here we have the chance of being told about a problem with
the simplest possible change in the source. That's a big win for me.



Very few compilers are conforming without options being set.

except MSVC doesn't have an option to complain last I looked.

Seems different enough to me to be important.

maybe...

I guess if it really were a big deal, I could make my header writing
tool replace "()" with "(void)" when writing out the headers, since it
doesn't really matter that much whether or not these headers look nice...
 
K

Keith Thompson

BGB said:
prototypes offer a unique power:
they make float and double arguments and return values work correctly;
they make pointer arguments and return values work correctly on some
systems (such as x86-64);
...

for example, if no prototype is used:
the compiler will automatically promote any float arguments to double;
Right.

any return value will be assumed to be "int", regardless of whatever is
there, which leads to bad results if either the argument if a float or
double (and thus goes into a different CPU register or similar), or
"sizeof(void *)!=sizeof(int)", or if a different CPU register is used
(say, for int and pointer returns).

Incorrect.

A prototype is just a function declaration that specifies the types of
the arguments. A non-prototype declaration always declares (perhaps
implicitly in C90, but still unambiguously) the return type.

This is not a prototype:

float func();

but it does declare that the return type is float.

In C90, if you call a function with no declaration at all (prototype or
otherwise), the compiler will assume that it returns an int -- but in
C99 that's a constraint violation.

[...]
 
B

Ben Bacarisse

Malcolm McLean said:
No, I mean the opposite.
In C++ we would write state::WASHINGTON. (state) WASHINGTON is more C-
like,

(type-name)expression already means something so I'd say that changing
that meaning is not very C-like. If, for some reason, you don't want to
go with namespaces as the solution, I think it would be more C-like to
use a syntax that has no current meaning. For example, state.WASHINGTON
or (state).WASHINGTON.

All this assumes that you have not special-cased enum tags. You've been
writing 'state' rater than 'enum state' so I've been assuming you have a
typedef in effect.
because

state BillGateseshangout = WASHINGTON;
and
president thatchapwiththefunnywig = WASHINGTON;

are both fine.

OK, but you are changing the meaning of initialisation and/or
assignment. Usually these involve a conversion so you'd have to say
when this conversion does not take place.

<snip>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,083
Messages
2,570,591
Members
47,212
Latest member
RobynWiley

Latest Threads

Top