is NULL-checking redundant in accessor-functions?

C

clarkcox3

Jens said:
More bullshit!

You are obviously deliberately handwawing, perhaps because you
find it amusing (what do I know?) As far as I can tell, you are
just trolling, but then again, what do I know?

I dare you: In which conforming implementation is it that what you wrote
above true?

On any implementation on a platform with protected memory that traps
dereferences of the NULL pointer (i.e most modern hosted
implementations). The program can be terminated on the dereference, and
never make it to the call to printf. On the other hand, there are
platforms (like older incarnations of the MacOS, or many standalone
implementations) where dereferencing the NULL pointer will get you
garbage values. On such a platform, x will get some platform specific
value, and the program will continue and successfully make the call to
printf.

This is what it means to have undefined behavior ... anything can
happen (as far as the standard is concerned). A perfectly conforming
implementation could, upon encountering that dereference of a NULL
pointer, become self-aware and start to sing "You Are My Sunshine".
 
K

Keith Thompson

Jens M Andreasen said:
More bullshit!

You are obviously deliberately handwawing, perhaps because you
find it amusing (what do I know?) As far as I can tell, you are
just trolling, but then again, what do I know?

It's a really bad idea to be simultaneously rude and wrong.

C99 6.5.3p4:

The unary * operator denotes indirection. If the operand points
to a function, the result is a function designator; if it points to
an object, the result is an lvalue designating the object. If the
operand has type "pointer to type", the result has type "type".
If an invalid value has been assigned to the pointer, the behavior
of the unary * operator is undefined.

A footnote says:

Among the invalid values for dereferencing a pointer by the unary
* operator are a null pointer, an address inappropriately aligned
for the type of object pointed to, and the address of an object
after the end of its lifetime.

So deferencing a null pointer (as in "x = *p;" above) invokes
undefined behavior. But what does that mean?

C99 3.4.3 (the definition of "undefined behavior"):

undefined behavior

behavior, upon use of a nonportable or erroneous program construct or
of erroneous data, for which this International Standard imposes no
requirements

NOTE Possible undefined behavior ranges from ignoring the
situation completely with unpredictable results, to behaving
during translation or program execution in a documented manner
characteristic of the environment (with or without the issuance of
a diagnostic message), to terminating a translation or execution
(with the issuance of a diagnostic message).

The C90 standard has similar wording, but my copy of the C99 standard
is easier to search and to cut-and-paste from. I can post the C90
standard's wording if you like.

In the code snippet above, "ignoring the situation completely with
unpredictable results" clearly includes the possibility of the
printf("Oops!\n");
statement being executed in the code snippet above.

It could also raise a signal or otherwise abort the program on the
attempt to dereference p. This is probably the most common behavior
on most modern systems.

Undefined behavior is undefined behavior. We sometimes jokingly talk
about "nasal demons", i.e., a conforming implementation could legally
make demons fly out your nose. Obviously no real implementation will
do this, but one that did would not be nonconforming for that reason.
I dare you: In which conforming implementation is it that what you wrote
above true?

I don't know. I was referring to what the standard allows, not to
what any particular real-world implementation does.

But there have been implementations in the past that represented null
pointers as all-bits-zero, didn't trap on attempts to dereference
address zero, and stored some particular value at that address (either
a zero or the string "(null)", I think). On such a system, running
the code snippet above will set x to whatever value happens to be
stored at address 0, and the string "Oops!" will be printed.

I wouldn't be at all surprised if there are current conforming
implementations that behave this way.
Most people around here understand the difference between the language
of a standard, which is almost always vague,

Standards can be vague in places, but I don't believe the C standard
is nearly as vague as you seem to think it is.
and the actual
implementatation, which is an interpretation of the language combined with
common sense (which, as it appears, is not so common anymore.)

Yes, most of us do. The real danger is assuming that everybody else's
"common sense" is the same as yours. Yours, I suspect, is based on
your experience with a relatively limited set of C implementations.
The C language, which is what we discuss here, doesn't have such a
limited scope.

Incidentally, while I was writing this I remembered that I have an
account on a system that I thought *might* behave as I described
above. It turns out that it doesn't, but it still provides an
interesting example.

I compiled and executed the following program on that system:

#include <stdio.h>
int main(void)
{
int *p = NULL;
int x;

x = *p;
printf("Oops!\n");
return 0;
}

and it printed "Oops!". Thinking I had come up with a concrete
example, I modified the program to print the value of x -- and it died
with an access violation error. Apparently the compiler had optimized
out the assignment, and therefore the dereference, because the value
of x was never used (which is a perfectly legal optimization). When I
added code that used the value of x, it could no longer do that
optimization. (The system in question is an old VAX/VMS system; the
VMS DECC compiler performs more aggressive optimizations by default
than the other compilers I tried.)

I get the same behavior on several other systems with:

gcc -O3 -ansi -pedantic -Wall -W tmp.c -o tmp

(In fact, "-O1" is all that's required.)

So this is a real-world case where the program, which invokes
undefined behavior, actually executes the printf statement. It's just
not for the reasons I had originally thought.

Undefined behavior is undefined behavior.
 
B

Brian Inglis

More bullshit!

You are obviously deliberately handwawing, perhaps because you
find it amusing (what do I know?) As far as I can tell, you are
just trolling, but then again, what do I know?

I dare you: In which conforming implementation is it that what you wrote
above true?

What happens if you run this code in an environment without any memory
protection?
Many systems have no memory protection, including many DOSes on
various micros, 16 bit Windows, MacOS on older hardware, and others.
Some machines have(/had) memory mapped I/O ports at low addresses.
What effect does a read of an I/O port have?
Can depend on what was last written to that I/O port.
Most people around here understand the difference between the language
of a standard, which is almost always vague, and the actual
implementatation, which is an interpretation of the language combined with
common sense (which, as it appears, is not so common anymore.)

It's nice to be able to test in a protected memory environment so that
the worst that can happen is an immediate segfault, if you're lucky.
If you're unlucky, you could trash your process' memory or file
control structures, possibly causing damage to input files, if your OS
kernel doesn't disallow writing to input files, before something
sufficiently drastic happens to terminate your program.

"There are more things in heaven and Earth, Horatio, than are dreamt
of in your philosophy"!
 
D

David REsnick

Jens said:
More bullshit!

You are obviously deliberately handwawing, perhaps because you
find it amusing (what do I know?) As far as I can tell, you are
just trolling, but then again, what do I know?

I dare you: In which conforming implementation is it that what you wrote
above true?

Most people around here understand the difference between the language
of a standard, which is almost always vague, and the actual
implementatation, which is an interpretation of the language combined with
common sense (which, as it appears, is not so common anymore.)

mvh // Jens M Andreasen

Having watched what people post for quite a while, you are quite wrong
in being rude to Keith. He is a good guy, provides lots of helpful
and accurate information and, unlike certain others, is invariable civil
while doing it. If you had hung out for a while you would surely
realize he is far from a troll.

His program above will not crash on HP-UX, as the "undefined behavior"
from a NULL pointer dereference there is yields the nul character as I
recall. This lets you do
printf("%s", NULL);
and not crash on that implementation. Should you count on that, even
if writing a program only for that platform? I don't think so...

Your attitute towards the standard is wrong IMHO. The belief that you
may do what works in the implementations that *you* know about or
are currently targeting the program in question to as opposed to what is
correct according to the standard leads to programs that ultimately are
not portable. Yes, it is sometimes necessary to do things that aren't
portable -- but such should be done with eyes wide open and restricted
to sections of the code KNOWN to be unportable.

-David
 
M

Michael Wojcik

Bullshit!

Keith's correct.
More bullshit!

From you, yes. Keith is still correct.
You are obviously deliberately handwawing,

There's nothing "obvious" about it, probably because your claim is
wrong.
perhaps because you find it amusing (what do I know?)

An excellent question, given your contributions to this thread.
I dare you: In which conforming implementation is it that what you wrote
above true?

In any conforming implementation where attempting to read a value
from a null pointer does not suspend program execution.

Here's one: XL C on AIX for many years provided an option to map a
read-only zero-filled page at virtual address 0. It used all-bits-
zero for its representation of null pointers, so dereferencing a
null pointer read or wrote virtual address 0. This option was
provided to support a large group of existing non-conforming programs
which assumed that a null pointer was equivalent to an empty string
for purposes such as passing it to strlen(). The existence of this
option did not render this implementation non-conforming - and if you
think otherwise I'd like to see C&V.

I daresay a conforming implementation for a system without virtual
memory management would quite likely not suspend execution if the
program dereferenced a null pointer. There are plenty of such
systems in the embedded world.
Most people around here understand the difference between the language
of a standard, which is almost always vague, and the actual
implementatation, which is an interpretation of the language combined with
common sense

Here (in comp.lang.c, which is where I'm reading this) we do under-
stand this difference. We also understand that the standard
guarantees what it guarantees, and that there are multiple implemen-
tations, and that our knowledge of them may not be comprehensive;
and so it is rather foolish to make wild assumptions about what all
conforming implementations may do in areas that the standard
deliberately leaves open.
(which, as it appears, is not so common anymore.)

Apparently it's not to be found in you, anyway.

Here's an idea: before you try to tell us what every single conforming
C implementation does, learn every single conforming C implementation.
Meanwhile, we'll stick with the standard.

--
Michael Wojcik (e-mail address removed)

Unlikely predition o' the day:
Eventually, every programmer will have to write a Java or distributed
object program.
-- Orfali and Harkey, _Client / Server Programming with Java and CORBA_
 
J

Jens M Andreasen

It's a really bad idea to be simultaneously rude and wrong.


OK Keith! I'll back off.

The discussion started out with if(p) and I never noticed the change to
if(*p) Sorry for the inconvenience.

Quoting M Teterin:

I always assumed, that checking the passed pointer `p' against NULL is
redundant, because, if it happens to be NULL at run-time (as a result of
programming error), trying to dereference it would result in SEGFAULT
anyway.

EOF quote.

mvh // Jens M Andreasen
 
J

Jens M Andreasen

The standard says so.

Keith, I'll try again (with a different approach):

Isn't it so that the standard says that 'if(*p)' is indeterminate?
At least that is what I get out of your debunking of my previous (rude
and wrong) message.

C99 6.5.3p4:

The unary * operator denotes indirection. If the operand points to a
function, the result is a function designator; if it points to an
object, the result is an lvalue designating the object. If the
operand has type "pointer to type", the result has type "type". If an
invalid value has been assigned to the pointer, the behavior of the
unary * operator is undefined.


As far as I can tell we are not dereferencing anything (yet) when we
compare p == NULL? The unary * operator is not in sight, is it?

Is there another section of the standard that supports that if(p) might
segfault after free()?

A practical consequence of this is that the following:

int *p = NULL;
int x;

x = *p;
printf("Oops!\n");

may or may not print "Oops!" in a conforming implementation.

The snippet above looks to me as related to if(*p) rather than if(p), no?


The original question was:

int *p = NULL;
if(p) // Segfault here?
...

And the extended discussion:

p = malloc(42);

if(p)
puts("p is not NULL");

free(p);

// Deliberatly not changing value of p yet
// ...

if(p) // Segfault here?
puts("p is still not NULL");

p = NULL;




mvh // Jens M Andreasen
 
P

pete

Jens said:
free(p);

// Deliberatly not changing value of p yet
// ...

if(p) // Segfault here?

Do you know what "indeterminate" means?

N869
7.20.3 Memory management functions
[#1] The value of a pointer
that refers to freed space is indeterminate.
 
D

Douglas A. Gwyn

Jens said:
free(p);
if(p) // Segfault here?

Quite possibly a segmentation (mapping) violation.
The value of p (not just *p) becomes indeterminate
after the call to free. free might map that range
of addresses out of the program's virtual address
space. Most implementations of free don't, but it
is allowed.
 
J

Jeremy Yallop

Jens said:
A practical consequence of this is that the following:

int *p = NULL;
int x;

x = *p;
printf("Oops!\n");

may or may not print "Oops!" in a conforming implementation.
[...]
I dare you: In which conforming implementation is it that what you wrote
above true?

Using a rather common implementation (gcc on Linux x86) the program
prints "Oops!" when compiled with optimization and causes a
segmentation fault when compiled without optimization.

$ cat oops.c
#include <stdio.h>

int main()
{
int *p = NULL;
int x;

x = *p;
printf("Oops!\n");
return 0;
}
$ gcc -ansi -pedantic oops.c -o oops
$ ./oops
Segmentation fault
$ gcc -O -ansi -pedantic oops.c -o oops
$ ./oops
Oops!

As Keith said, the behaviour is undefined, so either outcome is
conforming to the standard.

Jeremy.
 
N

Nils Weller

Jens M Andreasen said:
]
A practical consequence of this is that the following:

int *p = NULL;
int x;

x = *p;
printf("Oops!\n");

may or may not print "Oops!" in a conforming implementation.
[...]
I dare you: In which conforming implementation is it that what you wrote
above true?

I don't know. I was referring to what the standard allows, not to
what any particular real-world implementation does.

But there have been implementations in the past that represented null
pointers as all-bits-zero, didn't trap on attempts to dereference
address zero, and stored some particular value at that address (either
a zero or the string "(null)", I think). On such a system, running
the code snippet above will set x to whatever value happens to be
stored at address 0, and the string "Oops!" will be printed.

I wouldn't be at all surprised if there are current conforming
implementations that behave this way.

Two systems in current use that exhibit this behavior are AIX and HP-UX.
On these implementations, page zero of the virtual address space may or
may not be mapped. If it is mapped, it is read-only (attempting to write
to it will cause a segmentation violation) and filled with null
characters. If it is not mapped, any kind of access will cause a
segmentation violation.

I have to admit that I don't know whether this property is tunable on
AIX, but HP-UX for one permits you decide whether or not you want the
program to receive a SIGSEGV signal when you attempt to read from page
zero. HP's C compiler by default invokes the linker such that the
generated binary allows dereferencing of null pointers; gcc does not.

nils@Ariel:~> uname -a
HP-UX Ariel B.11.11 U 9000/785 2010249980 unlimited-user license
nils@Ariel:~> cat new.c
#include <stdio.h>

int
main(void) {
int *p = NULL;
int x;
x = *p;
puts("Oops!");
return 0;
}
nils@Ariel:~> cc new.c -o new
nils@Ariel:~> ./new
Oops!
nils@Ariel:~> chatr new | grep nulptr
nulptr references disabled
nils@Ariel:~> chatr -z new | grep nulptr
nulptr references enabled
nulptr references enabled
nils@Ariel:~> ./new
Segmentation fault (core dumped)
nils@Ariel:~>
 
K

Keith Thompson

Jens M Andreasen said:
Keith, I'll try again (with a different approach):

Isn't it so that the standard says that 'if(*p)' is indeterminate?
At least that is what I get out of your debunking of my previous (rude
and wrong) message.

C99 6.5.3p4:

The unary * operator denotes indirection. If the operand points to a
function, the result is a function designator; if it points to an
object, the result is an lvalue designating the object. If the
operand has type "pointer to type", the result has type "type". If an
invalid value has been assigned to the pointer, the behavior of the
unary * operator is undefined.


As far as I can tell we are not dereferencing anything (yet) when we
compare p == NULL? The unary * operator is not in sight, is it?

Is there another section of the standard that supports that if(p) might
segfault after free()?

There are two different issues here. The first is what happens when
you refer to the value of p (without dereferencing it). The second
is what happens when you dereference p.

The first issue:

If p either points to a valid object or is a null pointer, referring
to its value is ok. You can do something like
if(p)
to test whether p is non-null.

But those aren't the only possibilities. If p is uninitialized, or if
it previously pointed to a valid object but that object's lifetime has
ended, the pointer itself has an indeterminate value. You can examine
the bytes that make up that value (e.g., by memcpy()ing it to an array
of unsigned char), but any attempt to examine the value *as a pointer*
invokes undefined behavior.

For example:

int *p; /* p is indeterminate */
p = NULL; /* We can refer to the value of p, but
we can't defeference it */
p = malloc(sizeof *p); /* p points to an object
(assuming malloc() succeeded) */
free(p); /* the object no longer exists, and
p is now indeterminate */

This is a fairly subtle and obscure point, and yes, it does conflict
somewhat with common sense. When the object created by the malloc()
call ceased to exist, the value of p didn't change, but that same
value, which was valid before the free(), became an indeterminate
value after the free(). The same thing happens if the pointer points
to a local variable which vanishes when the function containing the
variable returns.

On most real-world systems, comparing an indeterminate pointer value
to NULL, though it invokes undefined behavior, will not actually cause
a trap. But imagine a system that performs the comparison by loading
the value into an address register, and that the very act of loading
the value into the register causes a hardware trap. The C standard is
designed to allow such a system to be conforming.

The second issue:

The more obvious point is that *dereferencing* either a null pointer
or an indeterminate pointer invokes undefined behavior. A null
pointer may safely be compared (for equality or inequality) to another
pointer value, but it may not be dereferenced.

So referring to the value of an indeterminate pointer invokes
undefined behavior (though most implementations allow it), and
dereferencing an indeterminate or null pointer also invokes undefined
behavior (and most implementations trap on attempts to dereference a
null pointer, though all bets are off for indeterminate pointers).
The snippet above looks to me as related to if(*p) rather than if(p), no?

Well, there's no if, but yes, it's related to if(*p).
The original question was:

int *p = NULL;
if(p) // Segfault here?
...

if(p) in that context is perfectly safe; a null pointer evaluates as
false.
And the extended discussion:

p = malloc(42);

We'll assume the malloc() succeeded.
if(p)
puts("p is not NULL");

free(p);

// Deliberatly not changing value of p yet
// ...

if(p) // Segfault here?
puts("p is still not NULL");

In this case, if(p) invokes undefined behavior. Whether this
manifests as a segfault depends on the implementation; commonly, it
won't. Even dereferencing p at this point is likely to "work", since
p may still refer to existing memory -- but as far as the standard is
concerned, the implementation is allowed to free the memory by
unplugging the corresponding RAM chip.
 
H

Hans-Bernhard Broeker

In comp.lang.c.moderated Jens M Andreasen said:
Isn't it so that the standard says that 'if(*p)' is indeterminate? [...]
As far as I can tell we are not dereferencing anything (yet) when we
compare p == NULL? The unary * operator is not in sight, is it?

Huh? What else if not the 'unary * operator' do you believe the '*'
in 'if(*p)' to be then?
 
J

Jens M Andreasen

We'll assume the malloc() succeeded.

We assume so, otherwise p would be NULL, no?
In this case, if(p) invokes undefined behavior.

Ehrmm, excactly what undefined behaviour?

It is not my intention to be rude, but I just have not the imagination to
figure out what you mean? Chapter and verse please?


mvh // Jens M Andreasen
 
J

Jens M Andreasen

Quite possibly a segmentation (mapping) violation. The value of p (not
just *p) becomes indeterminate after the call to free. free might map
that range of addresses out of the program's virtual address space. Most
implementations of free don't, but it is allowed.

What you say is correct, but in context wrong. So ...

Mmmm ... no! You would need a 'move' to get there and not a mere 'add'.
And again, it is *p that becomes indeterminate, not p.

The value of p was returned by malloc() and is valid for the implemntation
of p. Not because malloc has any insight of p, but just because the
definition of malloc!

If you really want p to be out of range, you will have to
call rand() or some such.



mvh // Jens M Andreasen
 
G

Gene Wirchenko

[snip]
This is what it means to have undefined behavior ... anything can
happen (as far as the standard is concerned). A perfectly conforming
implementation could, upon encountering that dereference of a NULL
pointer, become self-aware and start to sing "You Are My Sunshine".

A kinder, gentler nasal demon?

Sincerely,

Gene Wirchenko

Computerese Irregular Verb Conjugation:
I have preferences.
You have biases.
He/She has prejudices.
 
C

Chris Torek

What you say is correct, but in context wrong. So ...

Mmmm ... no! You would need a 'move' to get there and not a mere 'add'.
And again, it is *p that becomes indeterminate, not p.

You are wrong and Doug Gwyn is right, at least formally speaking.
(Implementations that actually catch the "if (p)" error after the
free(p) call are somewhere between rare and nonexistent.)

This issue is quite tricky, but it may become clear if you can answer
me this question: what is the value represented by the following
bit pattern?

11000011 11110101 01001000 01000000
(in hex: 0xc3 0xf5 0x48 0x40)

I will give you two possible answers (though there are more):

3.14 (a float)
1078523331 (an int)

and will I tell you that at least one of them is correct (and the
machine in question has a Pentium III CPU).

How will you decide whether those bytes represent a float or an
int? More importantly, whether I tell you the truth or lie -- it
does not really matter which -- how can it be that this same bit
pattern apparently represents two different numbers?

The answer lies in the *interpretation* of the bits. The bits
themseves are just bits. By themselves they have no deeper meaning.
It is only when I tell you "these are the bits of a float" that
they *mean* 3.14, or "these are the bits of an int" that they *mean*
1078523331.

What if I tell you, instead, that the machine is a Deathstation
9900, and the bits represent a pointer? What is the value of that
pointer? How will you interpret them?
The value of p was returned by malloc()

Originally, yes.
and is valid for the implemntation of p.

Valid until free()d, yes.

But what if free() changed something in the hardware so that the
very same -- unchanged -- bits in p, that *used* to mean "the
contents of the memory bank located in the south wing of the
building", now mean "please trigger the nuclear warhead"? (This
is, after all, the Deathstation. :) )

There is nothing in the C language that prohibits free() from
changing the way the machine interprets the bits. If certain
pointer bit patterns were like "int" before (always valid), and
are like "float" now (have trap representations), the simple act
of testing the value of "p" can fault.

*This* is how p can become invalid after free(): not by having its
stored bit pattern change, but rather by having the *meaning* of
those bits change.
 
K

Keith Thompson

Jens M Andreasen said:
What you say is correct, but in context wrong. So ...

Mmmm ... no! You would need a 'move' to get there and not a mere 'add'.
And again, it is *p that becomes indeterminate, not p.

And again (and again and again), the value of p becomes indeterminate
after free(p) is executed. On most implementations, evaluating p
won't cause any problems, and p will safely compare unequal to NULL --
which is of course a possible consequence of undefined behavior.

Really.
 
C

CBFalconer

Jens said:
What you say is correct, but in context wrong. So ...

Mmmm ... no! You would need a 'move' to get there and not a mere
'add'. And again, it is *p that becomes indeterminate, not p.

The value of p was returned by malloc() and is valid for the
implemntation of p. Not because malloc has any insight of p, but
just because the definition of malloc!

You haven't been listening. The thing that makes the value of p
indeterminate is the action of free(p). malloc always returns
something determinate UNTIL the value is passed to free.
 

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

Similar Threads


Members online

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,264
Latest member
FletcherDa

Latest Threads

Top