Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Alias


December, 2004: Alias

Herb Sutter (http://www.gotw.ca/) is a leading authority and trainer on C++ software development. Jim Hyslop is a senior software designer for Leitch Technology International. He can be reached at [email protected].


It was mid afternoon, and I was just wrapping up a discussion with Wendy when Kerry dropped by with a problem.

"Can you help me? My code doesn't work," he said.

Wendy smiled. "Sure. Show us the code."

Wendy and Kerry trooped over to Kerry's cubicle. I decided to tag along because I needed a quick break, and Kerry's cube was on the way to the coffee machine.

"I just wrote some code to update a variable," Kerry was saying, "but it doesn't seem to get updated. Here," he settled down in front of his workstation, fired up a text editor, and pointed to the offending line. It boiled down to this:

INCR_AND_ASSIGN( someVariable, someVariable );

"What's INCR_AND_ASSIGN?" Wendy asked.

"I use that function all the time in this module," Kerry said. "What it does is take its first argument, increments it, and assigns the original value into the second argument. It also does some logging, so that's why we call the function instead of just writing out a ++ and an = by hand."

"We?"

"Me and Bob."

"You use this...function...all the time?" asked Wendy.

"Uh-huh."

"Just like this?"

"Pretty much," Kerry confirmed. "Well, usually I increment one variable and assign into a different one, but in this case I happen to increment and assign to the same one."

"Wait a minute," Wendy broke in. "How is this thing defined?"

I helped Kerry find it. After removing nonessentials, it boiled down to this:

#define INCR_AND_ASSIGN( a, b ) b = a++

"Aha!" Wendy exclaimed. "Just as I suspected from the uppercase name. It's not a function. It's a macro! That must be the problem."

"Uh, why?" asked Kerry.

"Well, 'cuz macros are evil, and..."

"...a good whipping boy?" I interrupted. "Sure, macros are evil, but they're not responsible for world hunger. I wouldn't assume that it's just the macro's fault."

"Try it and see," Wendy challenged.

"Hmm. Well, let's write a little test," I said. I took over the keyboard and wrote:

int i = 0;
INCR_AND_ASSIGN( i, i );
cout << i << endl;

"So, Kerry, what do you think this will print?" I took the opportunity to slip into lecture mode.

"Uh, 0?" Kerry guessed. "That's what I want. It should increment i to 1 and assign the old value of 0 back into i. I'm just calling the function...er, the macro...for its other side effects."

"Uh-huh. It really expands to this." I replaced the macro call with what the macro would expand out into:

int i = 0;
i = i++;
cout << i << endl;

"Logically," I pontificated, "i++ gets evaluated first and sets i to 1. But it yields its old value, 0, and that's what then gets assigned back to i. Therefore, the output will be 0."

"I'm not so sure..." Wendy began cautiously.

I waved it away dismissively, sure of my reasoning. "Run it and you'll see. Go ahead."

"Okay." Kerry compiled the test program and ran it. The output came up:

1

I blinked and started to get an uneasy feeling. Kerry shifted uncomfortably. Wendy smirked.

After a short pause, Kerry asked: "Want me to try the other compilers?" I nodded weakly. Kerry tried the test under other compilers, and on many of them the output was 1, but sometimes it was 0.

"Er, ah, hmm," I said, not terribly intelligently.

"But why does it print 1 on most of the...?" Kerry started.

"Must be the macro," Wendy interrupted him.

"But still, why does it print 1 on most but not all of...?"

"Unimportant. Macros are just weird."

"Maybe it isn't the macro...?" I ventured.

Wendy ignored me. She took the keyboard, and replaced the macro with a function:

void IncrAndAssign( int& a, int& b ) {
  b = a++;
}

"There," she said, satisfied. "No more macro weirdness games. Now just tweak the test code to call the function..." She wrote:

int i = 0;
IncrAndAssign( i, i );
cout << i << endl;

"That will solve my problem?" Kerry asked.

"Yeah," and "I'm sure it will," both Wendy and I assured him in harmony. Wendy gave him back the keyboard. "Go ahead, try it."

Kerry shrugged, compiled our example on his usual compiler, and the output appeared:

1

Kerry hesitated, then: "Want me to, uh, try the other compilers...?" Seeing the expressions on our faces, he just went ahead and did it. As before, most compilers generated programs that printed 1, while a few generated 0. I noticed that one of the compilers that had been in the 1 camp before, when using the macro, was now in the 0 camp when using the function.

Both Wendy and I blinked together this time.

My uneasy feeling deepened. "Er, ah, maybe it isn't the macro...?" I repeated.

Snap. We all jumped at the sound. I had become so engrossed in the embarrassing dilemma that even after all this time I wasn't aware of the Guru's gliding up behind us until she snapped her tome shut. I think this time she was carrying the C Standard book [1].

"My apprentices, my acolyte," she greeted Wendy and me and then Kerry, respectively. "Have I not warned you of the perils of unspecified behavior? But this, this atrocity..." she trailed off, shaking her head.

"The macro?" Wendy offered in a small voice.

"No! The macro is not the atrocity here... or, at least, not the major one. The atrocity is that you are shooting in the dark!" She paused and fixed us with her gaze.

We wilted; she was right, we had just been making assumptions without really trying to understand the problem.

She continued: "You ignored the acolyte's key question, and are making random changes to try to make the problem go away. Unscientific, it is. Atrocious."

I couldn't resist the bait: "Which key question did we ignore?"

The Guru nodded toward Kerry. "Pray, ask your unanswered question again."

Kerry brightened at this. "'Why does it print 1 on most of the compilers, and 0 on others?' That question?"

"Indeed. Simply rearranging the code without understanding the reason for the behavior, my apprentices, is mere shooting in the dark. Without a flashlight, I might add. Do you not see that the function is really no different from the macro?"

"What?" I just had to burst out. "Functions are always better than macros!"

"Except, my child, when they are not," the Guru rebuked me firmly. "Here functions are no better than macros."

"They're not?"

"Not for this issue. Do you not see what the fundamental problem is?"

We just looked at her blankly.

"Apprentice, consider this parable." She wrote on the whiteboard:

int i = 0;
int j = i++ + i;
cout << j << endl;

She set down the marker. "Pray tell, what would you expect the result to be? Is the second i read before or after the increment occurs?"

Then the penny dropped partway, and I said: "I think I remember. It's unspecified behavior; a Standards-compliant compiler could do it either way. I've seen this before. I just can't remember what it's called, but it's weird and..."

Wendy closed her eyes tiredly. "Oh, brother. Sequence points. Right."

The Guru smiled. "Indeed. Your original code, i = i++, was trying to modify the same int twice without a sequence point in between. The parable I just wrote is trying to increment and read the value of the same int without a sequence point in between. This cannot be done, not portably at least. Compilers are granted great leeway to reorder or even parallelize expression evaluations, especially for fundamental types like ints."

"This really is sanctioned? Deliberate?"

"Oh, yes. As the Holy Standard proclaims, in Expressions 5:4: 'Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.'"

"I shouldn't have to know this stuff," Kerry grumbled.

The Guru got serious, and dropped her shtick for a moment. "You're right, Kerry. You shouldn't have to know this. But fortunately, there's a simple way to avoid it: Never write code that does things it shouldn't do between sequence points, like try to write into the same int, or other built-in scalar value, more than once in the same expression."

"Isn't that easier said than done?" Wendy objected.

"It can be," the Guru admitted. "Even in your function, the issue is aliasing." She pointed at the offending line:

void IncrAndAssign( int& a, int& b ) {
  b = a++;
}

"This function has the same problem as the macro because a and b are references, and so it's possible that they refer to the same int. That is, a and b are two alternative names—or pseudonyms, or aliases—for the same int variable. The way to avoid this is to write it assuming the worst case, that any references or pointers might be aliases for the same object, and then not doing too much work in the same statement. Like this..." She amended the code to read:

void IncrAndAssign( int& a, int& b ) {
  int temp = a++;	// ok, only modifies a
  b = temp;		// ok, only modifies b
}

"Beware thee aliasing," she resumed her Guru shtick.

"Right," I agreed, "because it can make code fall afoul of the sequence point rules."

"Oh, but not just that," the Guru was quick to correct. "Consider this parable." She wrote:

template<typename T>
void SetBothToMaxOfEither( T& a, T& b ) {
  if( a < b ) a = b;
  else b = a;
}

"The aliasing problem, do you see it?"

Now knowing what to look for, and because she'd helpfully named the variables the same, I was faster: "Yeah, if a and b happen to refer to the same object, then the object had better be safe for self-assignment."

"Indeed. And now this parable." She cleared the whiteboard and wrote afresh:

void AcquireLocksOnBoth( Mutex* a, Mutex* b ) {
  AcquireLock( a );
  AcquireLock( b );
}

"What say you?"

This time Wendy piped up: "Well, it had better be okay to call AcquireLock twice on the same mutex, because a and b could point to the same thing. I know that not all kinds of lock implementations are reacquirable like that."

"Even so, my child. Therefore, beware aliasing. Different pointers or references could refer to the same object. Wherever it could matter, as with potential sequence point issues or potential self-assignment concerns, take steps to ensure any aliasing is innocuous."

And she glided away, leaving us feeling only slightly better.

References

[1] British Standards Institute. The C Standard: Incorporating Technical Corrigendum 1. Wiley, 2003.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.