References in C

B

BartC

Shao Miller said:
On 6/26/2011 4:55 PM, BartC wrote:
That mixes the two points or seems to have more to do with the second
point that Mr. I. Collins had made, as far as I can tell. I was
specifically responding to the first point with that portion of the
response that you quoted.

A callee checking for a null pointer parameter needn't happen if we assume
that a callee is never passed a null pointer argument, with or without
references, right? As a language feature, I don't understand "references"
as providing an advantage with regards to this.

Do these "references" inject an implicit assertion before every call, thus
crashing the program (during execution) in the caller before reaching the
callee?

int * ip;
/* 'ip' is populated somehow */
func((assert(p), *p));

As it seems to be implemented in lcc-win32, it appears that a reference
parameter has a type distinct from a pointer type, so you can only pass a
reference, and references can never be null.

If that's the case, then there need be no checks for NULL, not even behind
the scenes.
 
J

Jens Thoms Toerring

Shao Miller said:
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)'.)

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

The benefit (as I understand it) is that the code doesn't even need to be
aware that something is a pointer:

a = b+c*d;

instead of:

if(a && b && c && d) *a=*b+*c*(*d);
else ... /* big headache... */
That mixes the two points or seems to have more to do with the second
point that Mr. I. Collins had made, as far as I can tell. I was
specifically responding to the first point with that portion of the
response that you quoted.
A callee checking for a null pointer parameter needn't happen if we
assume that a callee is never passed a null pointer argument, with or
without references, right? As a language feature, I don't understand
"references" as providing an advantage with regards to this.
Do these "references" inject an implicit assertion before every call,
thus crashing the program (during execution) in the caller before
reaching the callee?

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). This also
implies that the reference never has a "wider scope" than
what it references and it's thus guaranteed that when what
the reference "points" to goes out of scope also the refe-
rence does. That way you simply have no chance to pass on
a reference that does not "point" to an existing object to
a function. Thus you don't have to worry in the callee that
you might have received the equivalent of a NULL pointer.
Of course, that makes references as arguments unsuitable
for cases where receiving a "NULL pointer" is actually a
reasonable possibility.
Regards, Jens
 
J

James Kuyper

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

I don't understand the context of that page well enough to figure out
the answer to the following question:

In a C++ context, references are needed as arguments to implement
operators with side-effects (++, --, =, and compound assignment), in
order to give those overloads access to the value which needs to be changed.

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?
 
S

Shao Miller

Shao Miller said:
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.

The benefit (as I understand it) is that the code doesn't even need to be
aware that something is a pointer:

a = b+c*d;

instead of:

if(a && b && c && d) *a=*b+*c*(*d);
else ... /* big headache... */

That mixes the two points or seems to have more to do with the second
point that Mr. I. Collins had made, as far as I can tell. I was
specifically responding to the first point with that portion of the
response that you quoted.

A callee checking for a null pointer parameter needn't happen if we
assume that a callee is never passed a null pointer argument, with or
without references, right? As a language feature, I don't understand
"references" as providing an advantage with regards to this.

Do these "references" inject an implicit assertion before every call,
thus crashing the program (during execution) in the caller before
reaching the callee?

int * ip;
/* 'ip' is populated somehow */
func((assert(p), *p));
 
J

jacob navia

Le 27/06/11 02:19, Shao Miller a écrit :
And there needn't be any checks for null pointer values _without_
"reference types," even behind the scenes, as far as I can tell! So I
was asking about an "injected assertion" because that might be perceived
as a benefit that we don't currently enjoy. Otherwise, the "referring to
a valid entity" doesn't appear to be a benefit that "reference types"
would have over "pointer types"; you could still call a reference-taking
function with an argument that is "no entity," couldn't you?

References must be initialized to an object when they are defined. They
are always bound to the same object by definition, there are no
assertions.

You can't write:
int & a;

You MUST write

int p = 42;
int & a = p;

If you write
int & a = 76;

I will generate an internal constant variable initialized to 76 and
give that address to the reference.

But I have problems with references to arrays (when trying to fix the
bug you uncovered).
 
J

jacob navia

Le 27/06/11 00:54, Jens Thoms Toerring a écrit :
No, it's much simpler (at least if Jacob implemented it the
same way it's done in C++):

Yes, I do not want any incompatible semantics with c++ in
this area. Their design is already debugged.
 
S

Shao Miller

Shao Miller said:
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.

There's also a bit of C89 rationale, where if 'NULL' is '(void *) 0',
then 'if (func_ptr == NULL)' might be comparing a pointer-to-function
with a pointer-to-incomplete, which doesn't seem quite right. I'd
sooner do 'if (func_ptr == 0)' than do 'if (func_ptr == NULL)'.
Sure, the *caller* should know whether a pointer value it's passing is,
or might be, or definitely isn't a null pointer. But the function
itself doesn't.

Sometimes a null pointer is a valid value. Sometimes it isn't.
Reference parameters (in languages that support them) are for the
latter case.

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.
 
S

Shao Miller

As it seems to be implemented in lcc-win32, it appears that a reference
parameter has a type distinct from a pointer type, so you can only pass
a reference, and references can never be null.

If that's the case, then there need be no checks for NULL, not even
behind the scenes.

(Of course you obviously mean "a null pointer value" by "NULL".)

And there needn't be any checks for null pointer values _without_
"reference types," even behind the scenes, as far as I can tell! So I
was asking about an "injected assertion" because that might be perceived
as a benefit that we don't currently enjoy. Otherwise, the "referring
to a valid entity" doesn't appear to be a benefit that "reference types"
would have over "pointer types"; you could still call a reference-taking
function with an argument that is "no entity," couldn't you?
 
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)'.)

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

The benefit (as I understand it) is that the code doesn't even need to be
aware that something is a pointer:

a = b+c*d;

instead of:

if(a&& b&& c&& d) *a=*b+*c*(*d);
else ... /* big headache... */
That mixes the two points or seems to have more to do with the second
point that Mr. I. Collins had made, as far as I can tell. I was
specifically responding to the first point with that portion of the
response that you quoted.
A callee checking for a null pointer parameter needn't happen if we
assume that a callee is never passed a null pointer argument, with or
without references, right? As a language feature, I don't understand
"references" as providing an advantage with regards to this.
Do these "references" inject an implicit assertion before every call,
thus crashing the program (during execution) in the caller before
reaching the callee?

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).

That's not the same point as not having to test a pointer for having a
null pointer value, though. Already discussed elsethread, with
pointers, you can have:

int i, * const ip = &i;

and 'ip' should always point to the same entity.
This also
implies that the reference never has a "wider scope" than
what it references and it's thus guaranteed that when what
the reference "points" to goes out of scope also the refe-
rence does.

I believe that by "scope," you mean "lifetime." That seems pretty
similar to a pointer value becoming "indeterminate" when a pointed-to
object reaches the end of its lifetime.
That way you simply have no chance to pass on
a reference that does not "point" to an existing object to
a function.

But this just seems to shuffle around any point of failure or undefined
behaviour, doesn't it?

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

int main(void) {
int * ip;
ip = malloc(sizeof *ip);
/* 'ip' might be a null pointer value */
free(ip);
/* 'ip' could be null or indeterminate */
func(*ip); /* U. B. */
return 0;
}
Thus you don't have to worry in the callee that
you might have received the equivalent of a NULL pointer.
Of course, that makes references as arguments unsuitable
for cases where receiving a "NULL pointer" is actually a
reasonable possibility.

But you don't have to check for null-valued pointer parameters, either,
so how is this an advantage?

I want to mention that I think the "C reference types" idea seems nice
in some ways, but not in _this_ way (unless it becomes clear). :)
 
T

Tim Rentsch

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. 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.

If someone is interested in counting votes, I would have to
give this suggestion a thumbs down.
 
I

Ian Collins

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.
For "static" and "automatic" objects, if you pass '&x', there can be
little doubt that the called function gets a valid "reference" to an
object. This can be observed at translation time.

For "allocated" objects, if we check for allocation success (not
everyone does, of course), then there should be no doubt about having a
valid object reference.

Please consider:

void func(int& i) { /* ... */ }

If we have some pointer ('int * ip') with a null pointer value and we do:

func(*ip);

do we not simply move the point of failure up to the caller? Whereas with:

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.
 
K

Keith Thompson

jacob navia said:
References must be initialized to an object when they are defined. They
are always bound to the same object by definition, there are no
assertions.

You can't write:
int & a;

You MUST write

int p = 42;
int & a = p;

If you write
int & a = 76;

I will generate an internal constant variable initialized to 76 and
give that address to the reference.

Ok, then that's a difference between lcc-win's references and C++
references. In C++, you can only initialize reference to an lvalue.
 
K

Keith Thompson

Shao Miller said:
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.
There's also a bit of C89 rationale, where if 'NULL' is '(void *) 0',
then 'if (func_ptr == NULL)' might be comparing a pointer-to-function
with a pointer-to-incomplete, which doesn't seem quite right. I'd
sooner do 'if (func_ptr == 0)' than do 'if (func_ptr == NULL)'.

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.

[...]
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.
 
I

Ian Collins

Ok, then that's a difference between lcc-win's references and C++
references. In C++, you can only initialize reference to an lvalue.

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!
 
J

jacob navia

Le 27/06/11 06:50, Ian Collins a écrit :
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!
Of course you do not change the value of 76, I generate a (hidden)
object that holds an integer 76, then I assign to the reference
the address of that hidden object.
 
I

Ian Collins

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.

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.

It probably would be trivial, most other common languages use some form
of pass by reference and the concept is well understood. If anything it
would be easier to understand considering how tricky novices find pointers.
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.

They should behave very differently at compile time! foo(x++) will fail
to compile in C++ (the result of x++ is not an lvalue).
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.

The same applies with a pointer parameter.
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.

Operator overloading is probably the main reason in C++.
In C, these factors simply aren't present. References
add essentially no additional semantic power, and bring
with them a significant burden. 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.

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.
If someone is interested in counting votes, I would have to
give this suggestion a thumbs down.

I would consider abstaining!
 
I

Ian Collins

Le 27/06/11 06:50, Ian Collins a écrit :

Of course you do not change the value of 76, I generate a (hidden)
object that holds an integer 76, then I assign to the reference
the address of that hidden object.

Wouldn't it be better to restrict non-const reference initialisation to
lvalues? That would be more intuitive to most people. If you want a
reference to an rvalue, use a const reference.
 
I

Ike Naar

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;''.
 
A

Alan Curry

But this just seems to shuffle around any point of failure or undefined
behaviour, doesn't it?

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;
}

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

jacob navia

Le 27/06/11 09:16, Alan Curry a écrit :
void foo(int&iref)
{
iref = 42;
}

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

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

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

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
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top