References in C

J

jacob navia

Le 27/06/11 02:53, Tim Rentsch a écrit :
jacob navia said:
The lcc-win compiler is an experimental compiler to promote the
development of C as a language. Contrary to the main trends of
language design this days, lcc-win considers C not a dead language but
a language that can evolve and propose new features.

In this context, one of the extensions that lcc-win supports is
references, where the design is largely inspired from the C++ design.


What are references?

In fact, a reference is just a pointer. It is different from plain
pointers in two ways:

(1) It is always assigned to a concrete object when it is defined.
(2) Once defined, it will always point to that same object. It can't
be changed to point into another object.
(3) It is used with the '.' notation, as if it weren't a pointer.
(4) Instead of "*' the symbol "&" is used:
int i = 23;
int&IntReference = i;

[snip elaboration]

Let's look at the plusses and minuses.

Plus:

1. Reduced syntax - calls don't need '&' to take address,
uses don't need '*' to dereference, don't need 'const'
in declarations. . Frequency: common. New capability
offered: none, all these can be done currently using
small amounts of local syntax. Benefit: minor.

2. Null values avoided. Frequency: common. New capability
offered: none in the language (no additional semantics),
some in program checking (eg, like 'lint'). Benefit:
minor for program development, positive for program
verification. (See additional comments in Orthogonal.)

Orthogonal:

1. Null values avoided. I think this item more properly
belongs to program verification than programming language.
Avoiding null values doesn't add any semantics, it only
makes certain conditions impossible (presumably there
would be compiler errors). Obviously doing this has
value, but it doesn't have to be a language feature to
get the value. The proposed language feature is tied
to references, but it would be nice if a similar test
could be made for some pointer parameters. Or how about
other kinds of tests, like integer values being positive?
There are lots of useful and potentially useful kinds of
checking that could be done, but these should not be part
of the programming language, because they do not add any
language semantics, they only facilitate program checking.
A better model is either a lint-like separate checker, or
extra-linguistic progrm annotation ala __attribute__ in
gcc.

Minus:

1. Language is bigger. There is more to read and understand,
and it complicates the type system (as anyone who has
tried to read through the C++ standard can attest).
Frequency: ubiquitous for new developers, presumably low
for experienced developers. Cost: hard to quantify, but
not trivial.

2. Unobvious effects on program semantics. Because of how
non-lvalues are dealt with, calls that look similar may
have very different semantics; eg, if foo() accepts a
reference argument, 'foo( x )' and 'foo( x++ )' behave
very differently (and unobviously so as far as the call
site goes). Frequency: presumably low. Downside: for
cases where it matters, potentially large. Cost: low
frequency times large downside gives non-negligible cost.

3. Hidden side effects. Without references, when calling a
function it's immediately obvious if argument values can
be modified -- if there is an '&' (or an array is used),
then the argument might be modified, otherwise it can't
be. With references, any lvalue argument is potentially
modifiable; the only way to know is check the function
prototype. Frequency: high. Downside: additional effort
spent in program development. Cost: extra work on every
function call gives significant cost.

Summary:

In C++, the justification for references usually involves
struct's or class'es with member functions, and perhaps
the additional rules related to type conversions in the
presense of C++ 'base'/'derived' types.

In C, these factors simply aren't present. References
add essentially no additional semantic power, and bring
with them a significant burden.

In C it would allow to avoid the famous "array decay" problem where
arrays are converted into pointers. Using references this works as expected:

#include <stdio.h>

#define ELEMENTS 5
#define COUNTOF(array) \
(sizeof (array) / sizeof *(array))

typedef double a_five_doubles[ELEMENTS];

// Change the following line to
// void foo(a_five_doubles & array)
// and it works as expected
//
void foo(a_five_doubles array)
{
size_t i;

printf("sizeof a_five_doubles=%ld\n",sizeof(array));
for (i = 0; i < COUNTOF(array); ++i) {
array = 3.14159;
}
}

int main(void) {
a_five_doubles array;

for (int i = 0; i < COUNTOF(array); ++i) {
array = i;
}

printf("sizeof a_five_doubles=%ld\n",sizeof(array));
foo(array);

return 0;
}

Other advantages are that you can be sure that they point always to the

same object, and that they are not NULL. This improves safety of
programs


The one quality that
may offer value doesn't belong in the language, any
more than gcc's compiler-option style rules belong in
the language. Adding references to C gives a net result
that is overall a big minus.

You have greatly exaggerated the completely minor problems of learning
by newcomers, and minimized the safety improvements to the language.
 
S

Shao Miller

On 06/25/11 09:38 AM, Joe Wright wrote:
On 6/24/2011 17:09, jacob navia wrote:

[ Snipping everything }

What advantage does C++ reference have? None say I. Pass&var and accept
*var and all is solved simply. Tell me where I'm wrong.

For one, you don't have to test for NULL,

(By which you obviously mean a null pointer value. I don't use 'if (p
== NULL)' but 'if (!p)'.)

But I still don't fully perceive this particular benefit, here.

Well to put it another way, you can't test for a null value. With a
reference parameter, the value can not be a null value. Yes you can
contrive a case where one is passed, but I'm sure we can contrive cases
for just about any C undefined behaviour.

With a pointer parameter, NULL may be a valid value.

Ah yes. But what I'm trying to understand is the benefit/savings for
not having a "no entity" case to check for.

/* Pointers, not checking for a null value */
void foo(int * const ip) {
/* Might be U. B. if 'ip' is null */
*ip = 42;
return;
}

int main(void) {
int * ip = malloc(sizeof *ip);
foo(ip);
return 0;
}

versus:

/* References, not checking for a null value */
void bar(int & i) {
i = 42;
return;
}

int main(void) {
int * ip = malloc(sizeof *ip);
/* Might be U. B. if 'ip' is null */
bar(*ip);
return 0;
}

The first point of U. B. has just been shuffled up one level.

But if a function receives/reads (as opposed to constructs) a pointer
and takes appropriate responsibility...

/* Pointers, checking for a null value */
void foo(int * const ip) {
/* Callers: Please ensure 'ip' is non-null */
*ip = 42;
return;
}

int main(void) {
int * ip = malloc(sizeof *ip);
if (ip)
/* 'ip' is non-null */
foo(ip);
return 0;
}

and:

/* References, checking for a null value */
void bar(int & i) {
i = 42;
return;
}

int main(void) {
int * ip = malloc(sizeof *ip);
if (ip)
/* 'ip' is non-null */
bar(*ip);
return 0;
}

then both are safe. Thus, I think pointers and references are quite
comparable in this regard, and I don't perceive this particular advantage.
Well yes, that's the classic case of a function that has a pointer
parameter (or handles a pointer return) having the responsibility for
testing the value.

In C++ it is idiomatic to use pointers where NULL is a valid parameter
value (or return value) and references elsewhere.

Aha. I see. So can a construct like the following help to catch a
reference problem, assuming the program gets through the line marked
"First possible U. B."?:

/* References, checking for a valid reference */
void bar(int & i) {
/* Check for a valid reference */
if (NULL != &i)
i = 42;
return;
}

int main(void) {
int * ip = malloc(sizeof *ip);
/* First possible U. B. */
bar(*ip);
return 0;
}
 
S

Shao Miller

Shao Miller said:
On 6/24/2011 5:11 PM, Ian Collins wrote:
On 06/25/11 09:38 AM, Joe Wright wrote:
On 6/24/2011 17:09, jacob navia wrote:

[ Snipping everything }

What advantage does C++ reference have? None say I. Pass&var and accept
*var and all is solved simply. Tell me where I'm wrong.

For one, you don't have to test for NULL,

(By which you obviously mean a null pointer value. I don't use 'if (p
== NULL)' but 'if (!p)'.)

I prefer to be more explicit, but your mileage obviously varies.

Yes, it does. :)

We can divide the set of pointer values in three subsets, perhaps:
1. null pointer values
2. traps
3. valid pointer values

I tend to use the relational and equality operators to compare a pointer
value with those members of subset #3. 'if' by itself (and '!' by
itself) can "naturally" distinguish between members of #1 and #3, so I
save some typing.

I'm not going to try to change your mind, but my own preference is to
apply "if" and "!" only to value that are logically boolean, in other
words, where there's a false value and one or more true values, with no
meaningful distinction among the true values. The result if isprint(),
for example, qualifies; the result of strcmp() does not.

Aha! That makes perfect sense. And indeed the "natural" predicate I
consider is "is a valid pointer." Perhaps something like:

enum colour {
black,
red,
more_red,
still_more_red,
/* ... */
yellow,
/* ... */
blue,
/* ... */
};
#define IS_COLOUR(subject) (subject)
Ah, but it absolutely is "quite right". NULL is a null pointer
constant, which means it can be compared to a value of *any*
pointer type. It's the compiler's job to worry about how it's
defined, and about making it work correctly.

Excellent point. I must discard that rationale entirely. Thanks.
[...]
Sure. But:

void func(int& i) {
++i;
return;
}

int main(void) {
int * ip = NULL;
func(*ip);
return 0;
}

is still a problem, isn't it? It's just that the undefined behaviour
occurs in 'main' rather than in 'func', if 'func' had taken and worked
with a pointer.

Of course it's a problem. Using references doesn't prevent problems
with null pointers; nobody ever claimed that it would. It's mostly a
notational convenience.

Notational convenience is not disputed. I thought we were discussing a
very particular point here: "For one, you don't have to test for NULL."

These "references" certainly seem prettier than:

void func(int * const ip) {
#ifdef i
#undef i
#endif
#define i (*ip)
/* Work with 'i' */
i = 42;
{
/* Inner scope. Uh oh... */
enum { i = 5 };
}
return;
#undef i
}

int main(void) {
int i;
func(&i);
return 0;
}
 
L

lawrence.jones

Ian Collins said:
But you can initialise a const reference with an rvalue, which makes
sense. I hope Jacob made a typo, trying to change the value of 76 would
be interesting to say the least!

Hey, you can do it in many versions of FORTRAN, why not C as well? :)
 
K

Keith Thompson

jacob navia said:
In C it would allow to avoid the famous "array decay" problem where
arrays are converted into pointers. Using references this works as expected:

#include <stdio.h>

#define ELEMENTS 5
#define COUNTOF(array) \
(sizeof (array) / sizeof *(array))

typedef double a_five_doubles[ELEMENTS];

// Change the following line to
// void foo(a_five_doubles & array)
// and it works as expected
//
void foo(a_five_doubles array)
{
size_t i;

printf("sizeof a_five_doubles=%ld\n",sizeof(array));
for (i = 0; i < COUNTOF(array); ++i) {
array = 3.14159;
}
}

[snip]

I don't think that particular feature is all that useful. The size of
the array still has to be constant.

Unless you want to permit references to VLAs. (C++ doesn't, of course,
since C++ doesn't have VLAs.)
 
S

Shao Miller

Yes, and it's even easier to demonstrate than your example.

void foo(int&iref)
{
iref = 42;
}

int main(void)
{
int *whoops = 0;
foo(*whoops);
return 0;
}

"Easier": Agreed. Similar example given elsethread, although it was
missing an #include; oops.

void func(int & i) {
++i;
return;
}

int main(void) {
int * ip = NULL;
func(*ip);
return 0;
}

The use of the C++ reference didn't make testing for NULL unnecessary; it
merely made testing for NULL impossible.

What about with:

#include <assert.h>

int main(void) {
int * ip = 0;
int & i = *ip;
assert(&i);
return 0;
}

Does the assertion check for a, let's say, "null reference"?
 
K

Keith Thompson

Shao Miller said:
Aha! That makes perfect sense. And indeed the "natural" predicate I
consider is "is a valid pointer." Perhaps something like:

enum colour {
black,
red,
more_red,
still_more_red,
/* ... */
yellow,
/* ... */
blue,
/* ... */
};
#define IS_COLOUR(subject) (subject)

And for a pointer value p, the way to express the predicate "is a
valid pointer" is "p != NULL". The expression "p" is another way
to express the same predicate, but it's not *just* a predicate;
it also gives you the value of the pointer, if you care to use it.

If you choose to omit the "!= NULL", I won't say you're wrong
(because you're not); it's just not the way I do it.

[...]
Notational convenience is not disputed. I thought we were discussing a
very particular point here: "For one, you don't have to test for NULL."

In the same sense that you don't have to worry about a
const-qualified object changing its value. It *can*, if you program
does something with undefined behavior, but then the problem is
somewhere else in your code -- and there's a chance that your
compiler or other tool can catch the problem.

[...]
 
S

Shao Miller

Le 27/06/11 09:16, Alan Curry a écrit :

??????
This would crash in the calling function, not in the called function,
making the crash one step closer to the source of the problem.

(Or at least invoke undefined behaviour "sooner"; a crash mightn't be
mandated, right?)

I'm a little curious about the C++ Standard text for reference
initialization. I'm curious, for instance, if '*whoops' is actually
evaluated/read (please don't confuse with 'whoops', which I assume would
be) or if its "lvalue-ness" is simply "used." But maybe I'll look it up
later.
 
S

Stephen Sprunk

Le 27/06/11 09:16, Alan Curry a écrit :

??????
This would crash in the calling function, not in the called function,
making the crash one step closer to the source of the problem.

Why would it crash in the caller? The caller would simply take the
address of *whoops (&*whoops, which simplifies to whoops) and pass it to
the function. whoops is only dereferenced in the callee.

S
 
J

Jens Thoms Toerring

Ike Naar said:
No, it's much simpler (at least if Jacob implemented it the
same way it's done in C++): you can only create a reference
to an existing object since it must be initialized at crea-
tion and a reference can't be made to reference anything else
after it has been created. So neither

int &ir;

is legal (missing initialization) nor

int i, j;
int &ir = i;
ir = j;

(attempt to change what the reference references). [...]
In C++ the last one is not an attempt to change ``ir'' into a
reference to ``j'' (which is, as you said, impossible).
Instead, it's an ordinary integer assignment that copies the value
of ``j'' into ``ir'' (which is still bound to ``i''). It has the
same effect as ``i = j;''.

Uuups, yes, that wasn't real C++ - there actually isn't any
syntax for changing what a reference "points" to;-)

Regards, Jens
 
T

Tim Rentsch

Ian Collins said:
jacob navia said:
What are references?

In fact, a reference is just a pointer. It is different from plain
pointers in two ways:

(1) It is always assigned to a concrete object when it is defined.
(2) Once defined, it will always point to that same object. It can't
be changed to point into another object.
(3) It is used with the '.' notation, as if it weren't a pointer.
(4) Instead of "*' the symbol "&" is used:
int i = 23;
int&IntReference = i;

[snip elaboration]

Let's look at the plusses and minuses.

Plus:

1. Reduced syntax - calls don't need '&' to take address,
uses don't need '*' to dereference, don't need 'const'
in declarations. . Frequency: common. New capability
offered: none, all these can be done currently using
small amounts of local syntax. Benefit: minor.

The use of const would be no different to pointer parameters.

Not sure what you're saying here. Certainly there is a
difference between

void foo( int *p ){ ... }

and

void foo( int *const p ){ ... }

because in the second function body the pointer variable 'p'
cannot be assigned into. That question (and other similar
ones) is what I was meaning to address.

It probably would be trivial, most other common languages use some
form of pass by reference and the concept is well understood.

Many language of 1960's-ish vintage had or have reference
parameters (and Ada does now, which is more like 1980 vintage).
But I think it's an exaggeration to say "most other common
languages" have it; with high likelihood the probabilities
run very much the other way, and reference parameters are
generally discredited in contemporary languages (with arrays
sometimes an exception to that).

Also, please note that having reference parameters is very different
from having references as a general type constructor. It's possible
to provide Ada-style parameters without having a "reference" type
constructor, and that would be a better proposal.
If
anything it would be easier to understand considering how tricky
novices find pointers.

You can't be serious!

They should behave very differently at compile time! foo(x++) will
fail to compile in C++ (the result of x++ is not an lvalue).

I was commenting on the language feature proposed, not on the
references that C++ has; those would be an improvement in this
regard, but my intention was to discuss just the particular proposed
feature.

The same applies with a pointer parameter.

Hogwash. I know at a call site whether I'm passing a pointer,
without having to look at the prototype of the function being
called; if I'm passing a pointer then I might want to look
more carefully, but otherwise I don't have to. That condition
doesn't hold if the language has reference parameters.

Operator overloading is probably the main reason in C++.

Pretty sure it was so struct/class instances could be thought
of as "objects", but in any case something outside of what
is relevant in C.

I disagree that there is a big minus. I'd say the cost to benefit for
C is about even, with the reduction in clutter tipping the balance in
their favour.

This comment doesn't make sense. The reduction in clutter is
almost the only benefit there is in this proposal.

I might agree to a proposal that would allow a parameter with
a pointer type to be used like it was the pointed-to type (ie,
so '*' would not be necessary, and '.' could be used in place
of '->'). What is inexcusable is not requiring the '&' at
call sites, which has almost no benefit, enormous downside
risk, and costs to development both initially and on an
on-going basis.
 
S

Stephen Sprunk

No, it's much simpler (at least if Jacob implemented it the
same way it's done in C++): you can only create a reference
to an existing object since it must be initialized at crea-
tion and a reference can't be made to reference anything else
after it has been created. So neither

int &ir;

is legal (missing initialization) nor

int i, j;
int &ir = i;
ir = j;

(attempt to change what the reference references).

Is there a good reason why you can't? Logically,

&ir = j;

should do that, following the syntax of reference initialization.

S
 
A

Alan Curry

Le 27/06/11 09:16, Alan Curry a écrit :

??????
This would crash in the calling function, not in the called function,
making the crash one step closer to the source of the problem.

When I tried it with g++ the segfault happened on the "iref = 42" line.
That's what I expected, since this reference business is just syntactic
sugar, and underneath the hood the *whoops in the caller is really &*whoops,
with the & canceling the * so there's not really any memory access there, and
the iref assignment is really doing a memory store to the location pointed to
by the function argument, which is a pointer.

The reference stuff just disguises everything.

If you have a compiler than can point out the error on the foo(*whoops) line,
that's nice for you I guess. Even better if it does the check at runtime,
since any real problems of this nature would probably involve a variable
whose null-ness isn't known at compile time.

This should thwart any static analysis:

int *whoops = rand()==666 ? malloc(sizeof(int)) : NULL;
 
A

Alan Curry

What about with:

#include <assert.h>

int main(void) {
int * ip = 0;
int & i = *ip;
assert(&i);
return 0;
}

Does the assertion check for a, let's say, "null reference"?

You're right. It's not impossible to check for a null reference. You just
have to think outside the box.

#include <stdio.h>

void foo(int &iref)
{
if(&iref == NULL) {
printf("&iref is null. How about that!\n");
return;
}
iref = 42;
}

int main(void)
{
int *whoops = 0;
foo(*whoops);
return 0;
}

I'm sure this is all formally undefined. But it shows that references are
purely an alternate syntax for pointers and don't provide any real
"protection" against unexpected NULLs. You're just depending on your caller
to have already done the check.

All functions depend on their callers being sane; new syntax won't change
that.
 
A

Alan Curry

Aha! That makes perfect sense. And indeed the "natural" predicate I
consider is "is a valid pointer." Perhaps something like:

Exactly. In my mental type map, "boolean" is a very high level abstract type,
and pointers are among its subclasses.
These "references" certainly seem prettier than:

void func(int * const ip) {
#ifdef i
#undef i
#endif
#define i (*ip)
/* Work with 'i' */
i = 42;
{
/* Inner scope. Uh oh... */
enum { i = 5 };
}
return;
#undef i
}

The #ifdef portion is unnecessary and possibly even harmful. If you have a
conflicting definition of the identifier "i" you should be happy to find out
about it as soon as possible. undefining it just covers up a problem, which
may show up later anyway since you don't get back the original definition
after the end of the function.

So there really should be only 2 additional "ugly" lines, the #define and the
last #undef.
 
I

Ian Collins

You're right. It's not impossible to check for a null reference. You just
have to think outside the box.

#include<stdio.h>

void foo(int&iref)
{
if(&iref == NULL) {
printf("&iref is null. How about that!\n");
return;
}
iref = 42;
}

int main(void)
{
int *whoops = 0;
foo(*whoops);
return 0;
}

I'm sure this is all formally undefined. But it shows that references are
purely an alternate syntax for pointers and don't provide any real
"protection" against unexpected NULLs. You're just depending on your caller
to have already done the check.

All functions depend on their callers being sane; new syntax won't change
that.
The more postings I read on this thread, the more I'm coming round to
the opinion that references are probably not such a good idea in C after
all.

In C++, references are often used as return values, so there is seldom
any mixing of references and pointers. Reference returns are only
practical if there is a means of indicating an error (exceptions in
C++). Without viable reference returns, references will have limited
utility.
 
I

Ian Collins

Ian Collins said:
What are references?

In fact, a reference is just a pointer. It is different from plain
pointers in two ways:

(1) It is always assigned to a concrete object when it is defined.
(2) Once defined, it will always point to that same object. It can't
be changed to point into another object.
(3) It is used with the '.' notation, as if it weren't a pointer.
(4) Instead of "*' the symbol "&" is used:
int i = 23;
int&IntReference = i;

[snip elaboration]

Let's look at the plusses and minuses.

Plus:

1. Reduced syntax - calls don't need '&' to take address,
uses don't need '*' to dereference, don't need 'const'
in declarations. . Frequency: common. New capability
offered: none, all these can be done currently using
small amounts of local syntax. Benefit: minor.

The use of const would be no different to pointer parameters.

Not sure what you're saying here. Certainly there is a
difference between

void foo( int *p ){ ... }

and

void foo( int *const p ){ ... }

because in the second function body the pointer variable 'p'
cannot be assigned into. That question (and other similar
ones) is what I was meaning to address.

I see, I missed your point.
You can't be serious!

Oh I am! Look at the proportion of posts here that relate to pointers.
I was commenting on the language feature proposed, not on the
references that C++ has; those would be an improvement in this
regard, but my intention was to discuss just the particular proposed
feature.

So I see, I thought Jacob was following the C++ rules. Binding a
reference to an rvalue by creating an internal object is a fundamental
flaw in the proposal.
Hogwash. I know at a call site whether I'm passing a pointer,
without having to look at the prototype of the function being
called; if I'm passing a pointer then I might want to look
more carefully, but otherwise I don't have to. That condition
doesn't hold if the language has reference parameters.

Humbug. You know you are passing a pointer, but you still have to check
to see if the function can modify the values it points to.

Anyway, my opinion has shifted to the "not a good idea in C" camp, so
I'll stop now.
 
J

Joel C. Salomon

Ken Thompson implemented operator overloading for the Plan 9 C compiler
without adding references; see <http://9fans.net/archive/2001/05/482>.

References are needed for those operators which return lvalues: (++, --,
=, compound assignment, unary *, [], ->*), in order to return something
that can actually be used as an lvalue.

How does Ken Thompson's implementation of operator overloading for the
Plan 9 compiler deal with these issues without the use of references, or
something that works very similarly?

Those operator-functions take pointers (and, I think, return) pointers.

--Joel
 
J

James Kuyper

On 06/24/2011 06:11 PM, Ian Collins wrote:
The case for references in C isn't as strong as it is for C++, where
references are required for operator overloading.

Ken Thompson implemented operator overloading for the Plan 9 C compiler
without adding references; see <http://9fans.net/archive/2001/05/482>.

References are needed for those operators which return lvalues: (++, --,
=, compound assignment, unary *, [], ->*), in order to return something
that can actually be used as an lvalue.

How does Ken Thompson's implementation of operator overloading for the
Plan 9 compiler deal with these issues without the use of references, or
something that works very similarly?

Those operator-functions take pointers (and, I think, return) pointers.

If I understand you correctly, in order for that to work, the connection
between the operator overload functions and the corresponding
expressions would have to be different from that of C++. In C++, if
there's applicable operator overloads, then a=b is equivalent to
operator=(a,b). I have no idea whether or how Plan 9 C allows explicitly
calling operator overload functions. However, if it did, and used the
same syntax for doing so as in C++, the equivalent would have to be
something like *operator=(&a,b).

That in turn would imply that a Plan 9 C operator overload function
would have less freedom than a C++ operator overload. In C++, while the
normal semantics of the assignment operator imply an operator overload
which takes a reference argument, and returns a reference value, it's
perfectly legal to define an operator overload which does not actually
do so. However, if Plan 9 C does the equivalent of *operator=(&a,b),
then that freedom disappears.

I won't argue that it's an important freedom, but it would certainly be
a significant difference between C++ and Plan 9 C operator overloading.
 
T

Tim Rentsch

jacob navia said:
Le 27/06/11 02:53, Tim Rentsch a @C3{A9}crit :
jacob navia said:
The lcc-win compiler is an experimental compiler to promote the
development of C as a language. Contrary to the main trends of
language design this days, lcc-win considers C not a dead language but
a language that can evolve and propose new features.

In this context, one of the extensions that lcc-win supports is
references, where the design is largely inspired from the C++ design.


What are references?

In fact, a reference is just a pointer. It is different from plain
pointers in two ways:

(1) It is always assigned to a concrete object when it is defined.
(2) Once defined, it will always point to that same object. It can't
be changed to point into another object.
(3) It is used with the '.' notation, as if it weren't a pointer.
(4) Instead of "*' the symbol "&" is used:
int i = 23;
int&IntReference = i;

[snip elaboration]

Let's look at the plusses and minuses.

Plus:

1. Reduced syntax - calls don't need '&' to take address,
uses don't need '*' to dereference, don't need 'const'
in declarations. . Frequency: common. New capability
offered: none, all these can be done currently using
small amounts of local syntax. Benefit: minor.

2. Null values avoided. Frequency: common. New capability
offered: none in the language (no additional semantics),
some in program checking (eg, like 'lint'). Benefit:
minor for program development, positive for program
verification. (See additional comments in Orthogonal.)

Orthogonal:

1. Null values avoided. I think this item more properly
belongs to program verification than programming language.
Avoiding null values doesn't add any semantics, it only
makes certain conditions impossible (presumably there
would be compiler errors). Obviously doing this has
value, but it doesn't have to be a language feature to
get the value. The proposed language feature is tied
to references, but it would be nice if a similar test
could be made for some pointer parameters. Or how about
other kinds of tests, like integer values being positive?
There are lots of useful and potentially useful kinds of
checking that could be done, but these should not be part
of the programming language, because they do not add any
language semantics, they only facilitate program checking.
A better model is either a lint-like separate checker, or
extra-linguistic progrm annotation ala __attribute__ in
gcc.

Minus:

1. Language is bigger. There is more to read and understand,
and it complicates the type system (as anyone who has
tried to read through the C++ standard can attest).
Frequency: ubiquitous for new developers, presumably low
for experienced developers. Cost: hard to quantify, but
not trivial.

2. Unobvious effects on program semantics. Because of how
non-lvalues are dealt with, calls that look similar may
have very different semantics; eg, if foo() accepts a
reference argument, 'foo( x )' and 'foo( x++ )' behave
very differently (and unobviously so as far as the call
site goes). Frequency: presumably low. Downside: for
cases where it matters, potentially large. Cost: low
frequency times large downside gives non-negligible cost.

3. Hidden side effects. Without references, when calling a
function it's immediately obvious if argument values can
be modified -- if there is an '&' (or an array is used),
then the argument might be modified, otherwise it can't
be. With references, any lvalue argument is potentially
modifiable; the only way to know is check the function
prototype. Frequency: high. Downside: additional effort
spent in program development. Cost: extra work on every
function call gives significant cost.

Summary:

In C++, the justification for references usually involves
struct's or class'es with member functions, and perhaps
the additional rules related to type conversions in the
presense of C++ 'base'/'derived' types.

In C, these factors simply aren't present. References
add essentially no additional semantic power, and bring
with them a significant burden.

In C it would allow to avoid the famous "array decay" problem where
arrays are converted into pointers. Using references this works as expected:

[snip code example]

The same thing can be done with a pointer to array.
Other advantages are that you can be sure that they point always
to the same object,

The same thing is true of a pointer of type 'T *const'.
and that they are not NULL. This improves safety of
programs

I agree that preventing dereferences of null pointers can
improve program safety, but there are other and better
ways to do that than changing the language definition.

You have greatly exaggerated the completely minor problems of learning
by newcomers,

I don't see any reason to expect adding references to C would make
learning the language easier, and in fact quite the contrary - if
the language is bigger it's natural to expect that learning the
language will be harder and take longer.

Also, your listings of positive points disregards the potential
risks and negatives, which in my judgment (as outlined above)
clearly outweigh the positives.
and minimized the safety improvements to the language.

I quite appreciate the value of safer programs; I simply have
a different judgment about whether this aspect belongs in the
language definition or is better done using other tools.

More generally, if you want to be successful in advocating
changes to the C language, it's important to hear and appreciate
the opinions and judgments of other people, even when -- or
especially when -- they don't agree with your own.
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
474,093
Messages
2,570,608
Members
47,228
Latest member
ValentinCh

Latest Threads

Top