DEV Community

Cover image for C/C++ Pointer Alignment Style: A Justification
Jason C. McDonald
Jason C. McDonald

Posted on • Updated on

C/C++ Pointer Alignment Style: A Justification

Virtually all coding style issues are a matter of subjective opinion and personal taste. Tabs v. spaces, Allman v. K&R, operator padding v. none; wherever you stand, you almost certainly have a list of justifications for your chosen style, and you can guarantee the other camps do too.

Some languages, like Python, go as far as to define a large swath of standard style conventions (PEP-8). C and C++ have no such official, unified standard. As a result, there are countless permutations of style conventions in those languages. Just look at how vast AStyle's documentation is!

And that's O.K.! Every developer, project, and team is able to find the style conventions that work for them, and everyone wins. Personally, I will even switch between Allman, K&R, and Linux Kernal bracketing styles, depending on the project and my mood (although I only use one style per project).

However, there is one point of C and C++ coding convention I believe is beyond subjective opinion: pointer alignment.

(P.S. Yes, the title is a typesetting joke.)

Pointer Alignment: The Options

In case you need a refresher, there are generally three ways you can align pointer and reference tokens in C and C++:

// LEFT ALIGNMENT
int aVal;
int* aPtr;
int& aRef;
int& someFunc(int byVal, int* byPtr, int& byRef);

// CENTER ALIGNMENT
int aVal;
int * aPtr;
int & aRef;
int & someFunc(int byVal, int * byPtr, int & byRef);

// RIGHT ALIGNMENT
int aVal;
int *aPtr;
int &aRef;
int &someFunc(int byVal, int *byPtr, int &byRef);

// RELATED: Use of the * and & operators:
passByVal(*aPtr);
passByVal(*(aPtr+1));
passByPtr(&aVal);
passByRef(&aVal);
Enter fullscreen mode Exit fullscreen mode

So, should we use left, center, or right alignment? I'll give you a moment to choose a side here.

Final Jeopardy

...Ready?

Left Is Right

I would argue that the left alignment is (almost) always the best option! Naturally, I don't expect you to take my word for it, so here's my logic...

What's Your Type?

Whether or not a variable is a pointer or reference is a property of the type. In other words, this following is illegal.

int aVal = 5;
int *aPtr = aVal + 6;
std::cout << aPtr << std::endl;
Enter fullscreen mode Exit fullscreen mode

I deliberately used the right alignment to demonstrate my point. Can you see how it obfuscates the problem?

error: invalid conversion from 'int' to 'int*'

The variable aPtr is statically typed as a pointer to an int. That's a property of the variable itself, and has to be taken into account in much the same manner as if it were a bool instead of an int. The fact it is a pointer is literally part of the data type of the variable!

Counterpoint: Multiple Declarations

(Thanks to @bluma for pointing me in this direction.)

Advocates of right-alignment do helpfully point out that the following doesn't work like you'd expect:

int* a, b, c;
Enter fullscreen mode Exit fullscreen mode

a is a pointer to an int, while b and c are just integers. To achieve the desired effect, you'd need:

int *a, *b, *c;
Enter fullscreen mode Exit fullscreen mode

This, they say, is an argument for right-alignment. It is one point I'll concede to that camp, however, it's generally considered bad practice to declare multiple variables on one line anyway! Besides that, a would be uninitialized, and you should habitually initialize pointers as nullptr (or at least 0) to prevent undefined behavior from sneaking into your code.

So, while the above example is one argument for right-aligning a pointer, I'd argue you should never have this example alive in your code. Initialize one variable per line. You'll thank yourself later.

What's In A Name?

Let's consider another example:

int *aPtr;
std::cout << *aPtr << std::endl;
Enter fullscreen mode Exit fullscreen mode

Sure, the outcome may look obvious to you, but consider the following people:

  • Someone who hasn't already absorbed the essence of C++ into their very being yet.
  • Yourself at 2 A.M. without sufficient caffeine in your system.
  • Yourself after writing user documentation for four months, when your C++ knowledge has begun leaking out of your ear.

When we right-align a pointer, it makes the asterisk appear to be part of the name. Yes, that's dead obviously a wrong conclusion to draw, but when you're tired or out of practice, brain glitches abound. We can forget that the asterisk is both part of the data type and a unary operator in its own right.

This point is even more obvious with the ampersand (&):

int aVal = 5;
int &aRef = aVal;
std::cout << &aRef << std::endl;
Enter fullscreen mode Exit fullscreen mode

What's that going to print out? Not 5! It will print the address of the variable that aRef holds a reference to.

0x76178327ee0c

Imagine making that mistake somewhere in a messy bit of mathematics, with a few typecasts in just the right place to obscure the compiler error. You could be chasing your tail for hours!

Additionally, consider this:

int &someFunc(int byVal, int *byPtr, int &byRef);
Enter fullscreen mode Exit fullscreen mode

Even if you understand that the function is called someFunc, not &someFunc, you can see how easy it would be to visually "lose" the return type's & (or *) in the mess.

By aligning our pointer and reference symbols to the data type, we visually clarify the difference between its use in a type, and its use as an operator.

What About Center Align?

Great, so there goes one option. Now, what about center alignment?

int aVal = 5;
int * aPtr = & aVal;
int & aRef = aVal;
std::cout << * aPtr << std::endl;
std::cout << & aPtr << std::endl;
std::cout << aRef << std::endl;
Enter fullscreen mode Exit fullscreen mode

Two problems. First, and least important, that's just plain ugly to many developers.

Second, and far more importantly, you've just created a tripping hazard for your brain. Every time you see an * or &, you have to parse the context to determine if it is being used as an operator or part of the data type.

So, you could do this, but you'll be unnecessary creating more mental work for yourself and others. And more mental work means a higher probability of error.

How About A Combination, Then?

The first rule of style is consistency! If your convention is fraught with exceptions to the rule, then it's going to be far harder to maintain.

To see what I mean, try and work out what the rules are for this style:

int aVal;
int *aPtr;
int &aRef;
int& someFunc(int byVal, int *byPtr, int & byRef);

passByVal(* aPtr);
passByPtr(& aVal);
passByRef(& aVal);
Enter fullscreen mode Exit fullscreen mode

Look ridiculous? Actually, I can offer a quick one-liner of my logic for each piece, although I won't sport your intelligence with it. The point is, unless I offered a clear explanation of each rule in my style guide (assuming anyone would read it!), a new developer to my code would have to work all that out for herself, in a much vaster code base than this!

In other words, keep it simple.

Summary

I can hear one guy in the second row now: "That's all well and good, but I've been coding in C and C++ for over twenty years. I know the difference by now."

And I'm sure you do. But coding style isn't about you! Coding style, like most programming conventions, is about other people, your non-optimized self included. The behavior of code should always be made as obvious as possible. I believe I've demonstrated how left-aligned pointers do that.

  • In declarations, align the * or & to the type (left). This clarifies that the data type is a pointer or reference. Allow no exceptions to this rule.
  • The above also lends clarity to the obvious: * or & is never part of the name!
  • When the * or & is serving as an operator, align to its operand (right), e.g. the variable name.
  • BONUS RULE: Don't pad unary operators! This clarifies that they only have one operand - the one connected. (i++ is typically better than i ++).

(If you really, truly, want to pad unary operators, be sure to apply that to & and * as well, but only when they're used as operators!)

Let me demonstrate again how this looks in practice:

int aVal;
int* aPtr;
int& aRef;
int& someFunc(int byVal, int* byPtr, int& byRef);

passByVal(*aPtr);
passByVal(*(aPtr+1));
passByPtr(&aVal);
passByRef(&aVal);
Enter fullscreen mode Exit fullscreen mode

I hope I've made a clear argument for left pointer alignment! If I've overlooked some practical angle on this - something more objective than aesthetics - please comment below.

Top comments (7)

Collapse
 
bluma profile image
Roman Diviš

I personally use same format rules as You. But I’m missing one important thing for young c/c++ programmers - information about declaration of multiple pointer variables. Because “int* a, b, c” results in A being pointer. B and C are normal int variables. Thus you should write “int *a, *b, *c” to create three pointers. Basically I don’t use this and prefer to create three separate declarations.

Collapse
 
codemouse92 profile image
Jason C. McDonald • Edited

Thanks for pointing that out! That is indeed an argument for right-alignment, and I found it mentioned in technical documentation elsewhere.

You're also correct, however, that initializing three variables like that is typically bad form anyway. So, one could argue that if you avoid that practice as a rule, left-alignment still wins the day.

Edited the article to include all that. :)

Collapse
 
stefanomontani profile image
stefanomontani

I'm a young c++ programmer and a veteran c# programmer and I have to say that the notation with the asterisk aligned to the left makes more sense to me. Alas, if writing “int* a, b, c” results in the declaration of the first variabile as a pointer to int and of the others two as int variables, from a logical point of view this closes the question for me: the right syntax requires to attach the asterisk to variable (it is no more a problem of alignment). Or the language is flawed (LOL). Bad form is one thing, another thing is wrong syntax.

Though, to follow my preferences and habits (and VC++ default formatting) I think I will use the asterisk aligned to the left and the pratice to write a separate declaration for any variabile.

Collapse
 
jolb profile image
Jolon • Edited

I have to disagree with you regarding center alignment. It seems you have set up a bit of a strawman so you could say that center alignment is ugly.

Center alignment should be written like so

int aVal = 5;
int * aPtr = &aVal;
int & aRef = aVal;
std::cout << *aPtr << std::endl;
std::cout << &aPtr << std::endl;
std::cout << aRef << std::endl;
Enter fullscreen mode Exit fullscreen mode

for the same reason that you wouldn't write std::cout << & aPtr << std::endl; for left alignment.

You mention needing to parse the context to determine if a center aligned variable is being used as an operator, but I think the time spent is negligible because AFAIK you cannot multiply the int type by a value (as in, int * a could never be "int times a" so no time will be wasted parsing it).

As for why I believe center is better than left alignment, consider the following: void func(int* const* a). What is const? The answer is the pointer to the int is const, not the pointer to the pointer, although left alignment seems to (incorrectly) suggest the "outer" pointer is const. Compare this to center alignment and you get void func(int * const * a). No chance of a mix-up here. Now you just need to remember to read right to left: "a pointer to a const pointer, which points to an int".

Obviously, you could remember to read right to left when using left alignment too, but if you don't have enough caffeine, as you mentioned, you might forget this rule and read it incorrectly.

Collapse
 
ravi-prakash-singh profile image
Ravi Prakash Singh
Collapse
 
haujetzhao profile image
豪杰 赵

I'm a begining learner, in my view, the right-align pointer sucks. Really appreciate your post!

Collapse
 
jrbrtsn profile image
John Robertson

It's most important to be consistent. I occasionally use GNU indent to reformat source code.