[C++] Pointer arithmetics and const correctness

Started by
3 comments, last by Antheus 14 years, 10 months ago
It's so easy to break the const correctness using pointer arithmetics!

#include <iostream>

int* ConstCast(const int* p)
{
  int* a = 0;
  return p - a + a;
}

int* ConstCast2(const int* p)
{
  int* a = 0;
  while (a < p) a++; // actually equivalent to "a += p-a"
  return a;
}

int main()
{
  int i = 5;
  const int* p = &i;
  int* r = ConstCast(p);
  *r = 666;
  std::cout << i; // output 666 if "i" is declared non-const
}

To prevent the compilation of ConstCast we need that pointer subtractions which involve a const-pointer return a type that can not be added to a non-const pointer. To prevent the compilation of ConstCast2 we must forbid the comparison between const and non-const pointers; but we can always convert a non-const pointer to a const one before the comparison. So it is impossible to design pointer arithmetics without allowing trivial ways to break const correctness? PS1: This just mind training, I can not change the C++ specifications! PS2: I am sorry if this has been already discussed, but "pointer arithmetics" and "const correctness" are two topics so much discussed that I could not find anything on them together.
Advertisement
Yes, you can break const correctness like this. In fact, you can break const correctness even easier by using const_cast which is built into the language.

You can also break C++ in dozens of other ways, if you really want to.

The point is that the language expects you to at least try to do the right thing. You can do the wrong thing if you really want, but don't be surprised when it bites you.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Pointer arithmetic is only valid under certain conditions. You can point to an object (or an array of objects), and you can point one past the end. All other pointer arithmetic is undefined.

Lets assume it is allowed. Even then, you are not technically breaking the rules. Const means "read only through this reference". So in both your ConstCasts, you are never writing through the const reference you are given.

Finally, nearly all C++'s compile time protections (const, private) can be subverted by a sufficiently determined programmer.
Actually, according to the rules of pointer arithmetic, both of your examples are undefined behavior. In particular, if you pass an unaligned pointer you're probably not going to get the same value back. Ex:
  int * orig = (int *)0x0FF3;  int * r1 = ConstCast(orig);  std::cout << (void *)r1 << std::endl;  int * r2 = ConstCast2(orig);  std::cout << (void *)r2 << std::endl;

On MSVC 2008 you get back 0FF0 and 0FF4 rather than 0FF3.
Static code analyzers will warn of such situations. While not standard practice, I did use PCLint (or some similar tool) on a project some time ago, and it properly reported indirect modification of const value, even in some incredibly obscure call chains (5-6 functions deep, caused by temporaries).

While it made sense at some point, I don't know why C++ compilers don't include full analysis during compilation. In my experience, compilation is the fast part these days, it's linking that takes forever.

Also, this is a good example why it makes sense to use one of array wrappers, even a self-written one. C doesn't have const-guarantee to the degree C++ does, and pointer arithmetic is a C concept, C++ has iterators.

This topic is closed to new replies.

Advertisement