contradiction in TC++PL?

J

JKop

ES Kim posted:
JKop said:
If you use reinterpret_cast to convert a pointer value to an unsigned
integral value (assuming the integral type is large enough to hold the
value) and then reconvert it back, then you're left with a valid
pointer.

So while the following may be invalid:

int jack[6];

int* p = jack;

--p; //There may be overflow, for instance if p == 0

That is, if p is null pointer?
Surely you're joking, Mr. JKop. ;-)


The value for a null pointer is implementation defined.

Just because when you write:

int* k = 0;

it sets it to a null pointer, doesn't mean that the null pointer value is in
actual fact 0. Consider the following to be the definition of a pointer:

class *
{
public:
*& operator=(int value)
{
if ( !value )
{
//Set to null pointer value
}
else

...

}
};


-JKop
 
I

Ioannis Vranos

JKop said:
Wrong language dude.


Actually I had searched in C++98 unsuccessfully, that's why I fell back
to C90 (which is a subset of C++98 except of the cases where something
else is provided).


Anyway, regarding C++:


5.2.10

5 A value of integral type or enumeration type can be explicitly converted
to a pointer.64) A pointer converted
to an integer of sufficient size (if any such exists on the implementation)
and back to the same pointer type
will have its original value; mappings between pointers and integers are
otherwise implementation-defined.


The above means that you can't assign a pointer value to an unsigned,
increment the unsigned and reassign the result back to the pointer and
expect well-defined behaviour.


You can only assign a pointer to an integer than can store it, and
reassign that back.
 
I

Ioannis Vranos

JKop said:
int jack[6];

int* p = jack;

--p; //There may be overflow, for instance if p == 0

That is, if p is null pointer?
Surely you're joking, Mr. JKop. ;-)



The value for a null pointer is implementation defined.

Just because when you write:

int* k = 0;

it sets it to a null pointer, doesn't mean that the null pointer value is in
actual fact 0. Consider the following to be the definition of a pointer:

class *
{
public:
*& operator=(int value)
{
if ( !value )
{
//Set to null pointer value
}
else

...

}
};



A null pointer never points to a valid object.
 
T

Tom Widmer

ES Kim posted:
Here's a code fragment from TC++PL Special Edition, p291:

void f1(T a)
{
T v[200];
T* p = &v[0];
p--; // please note (my comment)
*p = a; // oops: 'p' out of range, uncaught
++p;
*p = a; // ok
}

And here's an excerpt from p92:

"The result of taking the address of the element before the initial
element is undefined and should be avoided."

The author says, as I understand, it's ok with ++p after p--, which I
think contradict the statement in the excerpt. What do you think?

Well consider this:

If you use reinterpret_cast to convert a pointer value to an unsigned
integral value (assuming the integral type is large enough to hold the
value) and then reconvert it back, then you're left with a valid pointer.

So while the following may be invalid:

int jack[6];

int* p = jack;

--p; //There may be overflow, for instance if p == 0

How would p == 0? p can't be a null pointer since it points to jack
(!)

The reason that it is undefined is that it is illegal to form a
pointer that doesn't point within an object, or one past the end of an
object, or to "0". A debugging implementation might generate code to
enforce this.
I'd advocate that the following *is* legal:


int jack[6];

int* p = jack;

unsigned long address = reinterpret_cast<unsigned long const>(p);

--address;
//We don't have to check for overflow because it's
//guaranteed not to occur with unsigned integrals.

p = reinterpret_cast<int* const>(address);

//Now 'p' points to one before the start of the array.

But that makes p an invalid pointer value, so the above cast and
assignment have UB.
In short, I would say that it's safe to increment or decrement a pointer
however you see fit, but just watch out for overflow!

Overflow isn't really the issue, rather it is a more fundamental one
that pointer rvalues must point within an object (or one past the
end). This is partly to allow for an implementation that has special
pointer registers where pointer values are checked for validity
whenever they are loaded into the register.
To simply say "it's UB to do this" even though there's a perfect logic
behind it to illustrate what's going on, is simply being formal and
pedantic.

I don't think that talking about overflow sheds any light on the
topic; overflow is not necessarily the underlying problem.

Tom
 
J

JKop

So what do you think of the following, does it exhibit UB in your opinion?:

int main()
{
long* p_blah = reinterpret_cast<long* const>(297239);
}


In my own opinion, it does *not* exhibit UB.


-JKop
 
I

Ioannis Vranos

JKop said:
So what do you think of the following, does it exhibit UB in your opinion?:

int main()
{
long* p_blah = reinterpret_cast<long* const>(297239);
}


In my own opinion, it does *not* exhibit UB.


It invokes implementation defined behaviour.



However


long x;

long *p=&x;

p--;

is undefined behaviour.



Some explanation on this: A pointer type is not required to be
implemented as an integer type.
 
T

Tom Widmer

So what do you think of the following, does it exhibit UB in your opinion?:

int main()
{
long* p_blah = reinterpret_cast<long* const>(297239);
}


In my own opinion, it does *not* exhibit UB.

Actually, I think my statement of UB might have been a bit strong -
it's implementation defined behaviour (which may of course be defined
to do anything). For example, on DOS, such expressions can be used to
access video memory IIRC. On other platforms they might cause a crash:

"A value of integral type or enumeration type can be explicitly
converted to a pointer. A pointer converted
to an integer of sufficient size (if any such exists on the
implementation) and back to the same pointer type
will have its original value; ___mappings between pointers and
integers are otherwise implementation-defined___." (my emphasis)

So it's up to the implementation what it does when casting 297239 to a
pointer. Casting the --val back to a pointer (in the previous example)
is similarly implementation defined.

Tom
 
I

Ioannis Vranos

JKop said:
For the love of Christ it does nothing!



It invokes implementation defined behaviour. This style is used in many
systems to access specific portions of memory that provide specific
functionality.


The assignment is implementation defined, with no side effects. However
if the assignment is not valid, any attempt to dereference the pointer
invokes undefined behaviour.


This is all "legal" stuff. The standard tries to provide as much
guarantees as possible, portably.


So for example, this assignment being implementation defined guarantees
that your program will not crash or something by this assignment alone.
 
R

Richard Herring

It invokes implementation defined behaviour.

For the love of Christ it does nothing!
[/QUOTE]
Hardly "nothing". It initialises a pointer with a bit pattern that may
not represent a valid hardware address. On some platforms which have
dedicated "address registers" and check their validity, that may be
sufficient to generate some kind of hardware exception.
 
J

JKop

Hardly "nothing". It initialises a pointer with a bit pattern that may
not represent a valid hardware address. On some platforms which have
dedicated "address registers" and check their validity, that may be
sufficient to generate some kind of hardware exception.


...puke-a-licious


Are you sure they're allowed do that?!


To those machines I barketh:

union
{
long a;
int* p_b;
} poo;


Now check the God damn address, I ddaarree ya!


-JKop
 
R

Richard Herring

[QUOTE="JKop said:
Hardly "nothing". It initialises a pointer with a bit pattern that may
not represent a valid hardware address. On some platforms which have
dedicated "address registers" and check their validity, that may be
sufficient to generate some kind of hardware exception.


...puke-a-licious


Are you sure they're allowed do that?![/QUOTE]

Why do you think the standard's so careful about what you can and can't
do with reinterpret_cast and pointers?
To those machines I barketh:

union
{
long a;
int* p_b;
} poo;


Now check the God damn address, I ddaarree ya!

Compilers know unions are dangerous... it won't load an address register
until you use p_b. It's up to you the programmer to make sure its
contents are valid at that time.
 
M

Malte Starostik

JKop said:
...puke-a-licious

Have you considered that this isn't something bad, but can help enhance
program safety as it causes a well-defined exception instead of Funny
Behaviour (tm) when an invalid pointer creeps in?
Are you sure they're allowed do that?!

Absolutely. See 5.2.10/5:

"[...] A pointer converted to an integer of sufficient size
(if any such exists on the implementation) and back to the
same pointer will have its original value; mappings between
pointers and integers are otherwise implementation defined."

As long as the defined case (back-and-forth conversion) doesn't result
in a hardware exception, the behiviour is fully compliant as I read the
above.
To those machines I barketh:

union
{
long a;
int* p_b;
} poo;


Now check the God damn address, I ddaarree ya!

Check 9.5/1 about unions. You must not inspect, i.e. read, poo.p_b
after assigning to poo.a, but before assigning to poo.p_b, I don't see
POD structs with an initial sequence in poo...

Regards,
Malte
 
J

JKop

Have you considered that this isn't something bad, but can help enhance
program safety as it causes a well-defined exception instead of Funny
Behaviour (tm) when an invalid pointer creeps in?


What the hell ever happened to the whole concept of POD?

Plain Ol' Data

PLAIN OL' DATA!


When I have the likes of:


struct Blah
{
int a;
double b;
int* p_k;

float** p_p_f;
};


I should be able to set it to whatever the hell I please!


Checking a pointer to see if it has a valid value (and perhaps throwing an
exception if not) is complete and utter total bullshit. It's inefficient
too. If I wanted such a mechanism, I'd explicitly ask for it:

int* k = ...

CheckPointer(k);


I'd like some clarity on the issue. Will the following behave identically on
all implementations, ie. will it output "I'm not a shit implementation!" on
all platforms?:


int main()
{
bool* p_boolean = reinterpret_cast<bool* const>(92729);

std::cout << "I'm not a shit implementation!";
}



-JKop
 
R

Ron Natalie

ES said:
Here's a code fragment from TC++PL Special Edition, p291:

void f1(T a)
{
T v[200];
T* p = &v[0];
p--; // please note (my comment)
*p = a; // oops: 'p' out of range, uncaught
++p;
*p = a; // ok
}
I don't have Page 92, but once you've done the p--, the rest
of the program is undefined. There is NO guarantee that you
can adjust the pointer outside the bounds of the array (including
one past the end) and be able to do anything with it furhter.
 
A

Andre Kostur

ES said:
Here's a code fragment from TC++PL Special Edition, p291:

void f1(T a)
{
T v[200];
T* p = &v[0];
p--; // please note (my comment)
*p = a; // oops: 'p' out of range, uncaught
++p;
*p = a; // ok
}
I don't have Page 92, but once you've done the p--, the rest
of the program is undefined. There is NO guarantee that you
can adjust the pointer outside the bounds of the array (including
one past the end) and be able to do anything with it furhter.

Yes you can. It is perfectly legal to form a pointer to the "one-past-the-
end" position of an array. You can then decrement it back into the range
of the array, and that's still legal. What you cannot do is dereference
that pointer (while it is one-past-the-end, or &v[200]). What is true is
that you may not form the pointer "one-before-the-beginning" position (&v[-
1]), or the "*two*-past-the-end" pointer (&v[201]). The program above
attempts to form the "one-before-the-beginning" pointer, and thus invokes
undefined behaviour. Even worse is that on the next line it ends up
dereferencing that pointer....
 
R

Ron Natalie

Andre said:
Yes you can. It is perfectly legal to form a pointer to the "one-past-the-
end" position of an array. You can then decrement it back into the range
of the array, and that's still legal.

That's what I meant the "including one past the end" binds to the "bounds
of the array" not "outside the bounds of the array." Sorry, for not being clear./
 
J

JKop

Yes you can. It is perfectly legal to form a pointer to the
"one-past-the- end" position of an array.


Why so?


Let's say a certain system has 32-Bit pointers.


A 32-Bit storage can have 4,294,967,296 unique different values.


If we take away the null pointer, we're left with 4,294,967,295 different
values. Let's say for example that a certain system isn't shit in that it
doesn't waste memory. Let's say that we have an array:


int blah[5];


And its elements' addresses are:

&blah[1] == 4,294,967,291
&blah[2] == 4,294,967,292
&blah[3] == 4,294,967,293
&blah[4] == 4,294,967,294
&blah[5] == 4,294,967,295

int* p = &blah[5];

++p;


What then?


What's the logic behind being able to have a pointer to one past the end of
an array?


-JKop
 
J

JKop

&blah[1] == 4,294,967,291
&blah[2] == 4,294,967,292
&blah[3] == 4,294,967,293
&blah[4] == 4,294,967,294
&blah[5] == 4,294,967,295

int* p = &blah[5];

++p;


Should've written:

&blah[0] == 4,294,967,291
&blah[1] == 4,294,967,292
&blah[2] == 4,294,967,293
&blah[3] == 4,294,967,294
&blah[4] == 4,294,967,295

int* p = &blah[4];

++p;
 
R

Ron Natalie

JKop said:
If we take away the null pointer, we're left with 4,294,967,295 different
values. Let's say for example that a certain system isn't shit in that it
doesn't waste memory. Let's say that we have an array:

The implementation is free to use the last char* value you propose as
long as it makes the pointer math work. The issue is that the "one
past the end" value needs to be testable to make paradigms like

for(ptr = array[0]; ptr != &array[SIZE]; ++ptr) { ..

work. &array[SIZE] isn't part of the array, but it has to
participate in the pointer math.

It's not "wasting memory", the trivial idea (not using 0 or 2**N - 1)
is just wasting a few bytes of address space.
 

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
473,995
Messages
2,570,236
Members
46,823
Latest member
Nadia88

Latest Threads

Top