C and C++ notes / Syntax notes

From Helpful
Jump to: navigation, search
Notes related to C and C++

Note: Some notes describe behaviour common to most variants - for C mostly meaning C89 (and a bit of C99), unless mentioned otherwise.

This article/section is a stub — probably a pile of half-sorted notes, is not well-checked so may have incorrect bits. (Feel free to ignore, fix, or tell me)


Array stuff

Conversion and casting fun

static_cast

Existing conversion, or

Usually safe, only unsafe when


dynamic_cast

The more robust version to static_cast when dealing with object hierarchies.

Does more checking, is slightly slower. May return a null pointer.

You can sometimes argue that you know a static_cast will do the right thing and use that instead.


const_cast

reinterpret_cast

C-style cast (type)value

Function-style cast type(value)

Pointer related stuff in C and C++

On pointer aliasing

Hairy bits and common misunderstandings

volatile

(verify)

Volatile basically means "this value might be changed by something else than the current thread of execution. Compilers, do not optimize this by keeping it in a CPU register. Fetch it from RAM every time my code refers to it."


This may sound useful for data structures shared between threads, but if you need them to be atomic and safe, volatile will not necessarily guarantee that.


There are specific cases that are fine.

Almost all of these cases are narrowly defined cases, informed cheating, and often center on simple operations on primitive types.

Volatile also has uses in memory mapped IO, and also low level code such as interrupt handlers, and some kernel guts, some embedded code.


...but many argue that until you can explain succinctly why your case is not one of the bad cases, you should probably not use it.

In part because in more complex cases, you may just be throwing in the towel about transactional correctness, races, and speed, possibly all at the same time.



Chances are a regular progammer won't need it much, possibly ever (in part because pointers are a different story anyway - see pointer aliasing).

const

This article/section is a stub — probably a pile of half-sorted notes, is not well-checked so may have incorrect bits. (Feel free to ignore, fix, or tell me)


const was intended as "I'm not planning to change this. Compilers, you can assume it won't change, if that helps you any".

But it isn't.


It's a contract of sorts, an agreement of what you will do.

Sure, the compiler will give you a compilation error when you try to alter the contents of a const variable that in its scope is declared const

...but that's the point: constness is part of the type in a scope, not a property of the thing it points to. In specific cases they are the same thing.


Const used on functions and APIs goes a little further. It avoid some basic mistakes such as misunderstandings of an API, and useful to some degree as a self-documenting thing.


Neither is a hard restriction on what can happen.

You can break this promise with basic casting.

And with many steps inbetween, this may well happen. Often in a way that is actually safe


Which means, Long story short, const variables cannot be considered immutable. You can often get away with doing so, but it is not guaranteed.


Note that there is some variation.

  • const values
mostly things within a scope.
  • const pointers/references
which implies that said binding will not change within a scope
not about the value, it can change as much as it wants
  • then there are const pointers to const values
  • Constness in value declarations
...particularly in virtuals...
are predeclared constness that things implementing an interface have no choice but to adhere to
because of the compiler hissyfit if your implementations try to
also useful to e.g. signal your interface/API that you intend to have no side effects.
Not that they couldn't, but it's useful to signal intent of the architecture.


Many people avoid const a bit, because they know it as a "the compiler whines at me unless I add more casting" thing.

And most, including me next week, are a little fuzzy on the important details, opting to know little more than that it's safe to make the compiler happy when it complains about lib-c string function stuff.


Constness details mostly apply within a particular scope (regularly a function, either via parameter definitions, or by locally declared variables. Occasionally also used on globals). For example:

const  unsigned char  byte_mask = 255; 
//also has to be initialised now, because it cannot be assigned to later


Implications of use

In theory, there are two major ideas about how const could help things.

One is that the compiler could make more assumptions about data not changing, and could optimize by not needing to copy as much data in some types of operations.

However, const variables cannot be safely considered immutable (more explanation below), which means the compiler can do only some minor optimizations.

It is also not a strong enough guarantee to be useful in parallelism.


The other idea is safety between functions, which could be useful in interfaces, to keep people from doing things they shouldn't be doing.

This is arguably the best use of const: signaling to other coders (reading the function definitions, or waiting for compiler errors) that they are not supposed to alter something, so they can not no so accidentally, only subversively. Resulting bugs (or features) can be attributed to the subverting programmer.

For example, it lets you pass by reference with a little more peace of mind.



More detail

In particular pointers make things more complex.

Consider you can declare both a const pointer and a non-const pointer to an actual value. You couldn't change the value via the const one, but you can via the constless variable -- which breaks the contract of the const variable.

You liekly wouldn't do that within the same scope, but it easily happens when you have functions are declared with constness in their parameters (e.g. printing or logging a given string).

You can also just cast the constness away. You have to do so explicitly, and if you understand all of the details you'll know it's a bad idea, but you can.



Syntax

The syntax can be confusing, particularly when you mix in pointers, and also declare that they are const. For example, what's the difference between:

char *s;
char const *s;
char * const s;
const char *s;
char const * const s;

To read those, the simplest rule (of thumb) is to read from right to left. Respectively:

  • s is a (non-const) pointer to a char
  • s is a (non-const) pointer to a const char
  • s is a const pointer to a char
  • s is a (non-const) pointer to a const char (same as the second)
  • s is a const pointer to a const char

Const applies to the thing at its left, unless there is nothing there, then it applies to the thing at its right. This is why const char and char const cases above are equivalent. In general it's probably least confusing to write and rely on the at-the-left default. Easier to read.


It also helps to know what happens with C++'s &-type passing. Well, basically what you would expect: you can pass an object without copying it it, and also say that it shouldn't be altered. Quite comparable to using a pointer to a const object.


Note that const can sometimes be a little safer than using #DEFINEs.


Casting away

Casting away constness can lead to undefined behaviour in some cases, because of compiler optimizations. (also depends a little on scoping details)

Consider:

const int a=1;
int   *b = (int *)(&a); //take address, make a pointer via which we can alter it.
*b=2
cout << a;

You might expect this to print 2, and without the const it would. With the const it most likely prints 1, because the compiler assumed that a is 1 by definition and can bake that value into any of the uses (most of those things: ...at this scope).


Note: It's stricter style to do that cast like:

int   *b = const_cast<int *>(&a);

...in that with this, the compiler will complain if the cast changes anything other than the constness (or volatileness). It's otherwise equivalent, so in the above example it would mean the same error. It simply doesn't make sense to try the above. Or to do it in functions called from a scope that assumes constness, for much the same reason. Casting away constness should probably only be done when you can come up with a convincing reason.


Further notes
  • a string literal has type "array of n const char"


See also

Various styles of pointers