OpenSees Cloud

OpenSees AMI

Pleasing the Compiler

Original Post - 08 Nov 2023 - Michael H. Scott

Visit Structural Analysis Is Simple on Substack.


Around the transition from G3 to OpenSees, the Visual Studio (VS) compiler was not fully compliant with the then current ISO C++ standard.

There were perhaps many items out of compliance, but the non-compliance that drove us nuts was the scoping rule on variables defined in the initialization statement of for loops. The VS compiler kept these variables in scope after the loop body instead of limiting the scope to only inside the loop body (the ISO compliant behavior).

As a result, consecutive for loops like shown below would be perfectly fine on Linux compilations but would fail in VS due to multiple definitions of the i variable.

for (int i = 0; i < N; i++) {
   // Do some stuff
}

for (int i = 0; i < N; i++) {
   // Do some other stuff
}

To please the VS compiler, we modified consecutive for loops to define i before the first loop and remove the variable declaration from the loop initialization.

int i;
for (i = 0; i < N; i++) {
   // Do some stuff
}

for (i = 0; i < N; i++) {
   // Do some other stuff
}

Perhaps there was a VS compiler setting for compliant loop initializations, but StackOverflow and StackExchange didn’t come around until about 10 years later, so finding obscure programming solutions was next to impossible.

Today, you can find artifacts of us pleasing the VS compiler back in 1999. For example, in the ZeroLength::setUp() method you’ll see two for loops in the same scope with i defined out front.

int i;
for (i=0; i<2; i++)
  theNodes[i] = 0;

//
// Set up the local x, y, z axes
//

// create transformation matrix of direction cosines
for (i=0; i<3; i++) {
  transformation(0,i) = x(i)/xn;
  transformation(1,i) = y(i)/yn;
  transformation(2,i) = z(i)/zn;
}

Several other methods in the OpenSees framework define loop variables outside the first for loop–it’s not some quirk of the ZeroLength element.

However, there are instances where defining the loop variable before the first for loop is the desired implementation. For example, in SectionAggregator::getStressResultant() and comparable methods in the SectionAggregator class, you’ll see that the loop variable i is carried over from one for loop to the next.

int i = 0;

int theSectionOrder = 0;
    
if (theSection) {
  const Vector &sSec = theSection->getStressResultant();
  theSectionOrder = theSection->getOrder();
    
  for (i = 0; i < theSectionOrder; i++)
    (*s)(i) = sSec(i);
}
  
int order = theSectionOrder + numMats;

for ( ; i < order; i++)
  (*s)(i) = theAdditions[i-theSectionOrder]->getStress();
  
return *s;

In this case, we weren’t trying to please the VS compiler–we were putting section forces into the proper entries of the concatenated section force vector. Sure, we could have coded these loops a little differently. But it ain’t broke, so we ain’t gonna fix it. Not this one.