Global variable in anonymous namespace changes value without call to set()

I'm working on a program that utilizes a variety of measurable fluid properties. The imperical equations that governs their behavior were derived in a metric system, but used non-standard SI units. I'm also in the US, so display in the American Engineering system of units is desirable as well. To handle this, I'm writing a Measurement System capability into the program. The general idea is to take in input in the user's desired unit system, convert it to the system utilized by the base equations, and then convert the returns back to the user's chosen unit system.

I considered making a class with all static members to protect the measurement system and return strings and conversion factors as necessary. After learning about anonymous namespaces, I've chosen to use that implementation instead. I have a set function in the MSys namespace that assigns the value to the variable containing what measurement system is in use. I call various functions that return values based on the selected system (conversion factors and unit strings).

Using the debugger in Code::Blocks, I'm tracking the program's execution. I watch several instances where the getter-like functions properly return the value based on what I've set the measurement system variable to. At one particular call to a return value function, it suddenly behaves as if the default measurement system is selected.

What would cause such an event to occur? What sort of examples from my code would you need to help me diagnose the problem? (I have 2200 lines of code directly related to handling the behavior of the measurement system and the values it returns.)
when a program gets that big AND you have this good a handle on what the problem looks like, it is usually worth trying to re-create the same problem with a tiny example program.
Well normally you would provide the smallest possible program that illustrates the problem. The key here is smallest possible. The small program should compile without errors or warnings and be easy to duplicate the problem.

Since you seem to know how to use your debugger you should be able to backtrace through the problem section to see exactly when that value is changing. Examine your call trace to aid in the backtrace.

By the way since you mentioned an anonymous namespace you should at least know the file where the problem occurs since accessing an anonymous namespace from another compilation unit problematic. Hopefully you have limited the number of functions that can access that anonymous namespace.




I've condensed it to (only) about 225 lines of the relevant code. Applicable notes are on lines 15, 51, 96, 110, 140, 164, 218, & 223.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// From msys.h

enum class UnitSystem : char
{
	base, SI, FPSF, FPSR, CGSC, CGSK, MTSC, MTSK
};

namespace MSys
{
    namespace // Anonymous namespace to protect the unit system
    {
        UnitSystem US = UnitSystem::base;
    }

    // Function in which the system is wrong
    template <typename T>
    T densityToBase(T input)
    {
        static_assert(std::is_arithmetic<T>::value, "Type must be numeric");

        switch (US)
        {
            case UnitSystem::base:
            case UnitSystem::SI:
                return input;
                break;
            case UnitSystem::MTSC:
            case UnitSystem::MTSK:
                return input / 1e3;
                break;
            case UnitSystem::CGSK:
            case UnitSystem::CGSC:
                return input / 1e-3; // Convert g / cm^3 -> kg / m^3
                break;
            case UnitSystem::FPSR:
            case UnitSystem::FPSF:
                return input / 62.427961e-3; // Covert lbm / ft^3 -> kg / m^3
                break;
        }
    }

    // Other function templates
}

// From msys.cpp

#include "msys.h"

namespace MSys
{
    // No problems with this function
    std::string temperatureUnit()
    {
        switch (US)
        {
            case UnitSystem::base:
            case UnitSystem::SI:
            case UnitSystem::CGSK:
            case UnitSystem::MTSK:
                return "K";
                break;
            case UnitSystem::CGSC:
            case UnitSystem::MTSC:
                return "C";
                break;
            case UnitSystem::FPSF:
                return "F";
                break;
            case UnitSystem::FPSR:
                return "R";
                break;
        }
    }

    // Other similar functions for string returns
}

// From helmholtz.cpp
#include "msys.h" // Actually included in helmholtz.h which would be included by helmholtz.cpp

namespace helmholtz
{
    // Function I'm trying to call when error occurs
    void reduce(num_t T, num_t rho, num_t& delta, num_t& tau)
    {
        delta = rho / rho_c;
        tau = T_c / T;
    }

    void enlarge(num_t& T, num_t& rho, num_t delta, num_t tau)
    {
        rho = delta * rho_c;
        T = T_c / tau;
    }

    // Function calling reduce() when error occurs
    num_t psat(num_t T, num_t& rho_f, num_t& rho_g)
    {
        using namespace akasaka;
        using akasaka::gamma;   // So as to not confuse with the gamma[] as part of the basic equations
        using std::abs;     // Without the 'std::' it computes abs() with an integer return == BAD
        //int iter = 0;
        rho_f = rho_tf, rho_g = rho_tg;
        num_t delta_f = 1, delta_g = 1, tau = 1;

        //cerr << "T = " << T << " | iter = " << iter << endl;
        //cerr << "rho_f = " << rho_f << " | rho_g = " << rho_g << endl;
        //cerr << "delta_f = " << delta_f << " | delta_g = " << delta_g << endl << endl;
        
        // Notice the error in the MSys namespace function calls here
        reduce(MSys::tempToBase(T), MSys::densityToBase(rho_f), delta_f, tau);
        reduce(MSys::tempToBase(T), MSys::densityToBase(rho_g), delta_g, tau);

        num_t dJ = 0;
        num_t dK = 0;
        //cerr << "Start iterating\n" << endl;
        do  // The Akasaka loop
        {
            /* Big, complicated math loop */
        }
        while (dJ + dK > 10e-13);

        enlarge(T, rho_f, delta_f, tau);
        enlarge(T, rho_g, delta_g, tau);

        //num_t psat_f = p(T, rho_f);
        num_t psat_g = p(T, rho_g); // rho_g tends to be more slightly accurate to the test data

        //cerr << "\niter = " << iter << " | T = " << T << endl;
        //cerr << "rho_f = " << rho_f << " | rho_g = " << rho_g << endl;
        //cerr << "psat_f = " << psat_f << " | psat_g = " << psat_g << endl << endl;
        return psat_g;
    }

}

// From state.cpp
#include "msys.h" // (Through state.h)

// Function calling helmholtz::psat() when error occurs
void State::setState_Tsat(helmholtz::num_t T)
{
    temperature = T;
    pressure = helmholtz::psat(temperature, density_f, density_g);
    density = density_g; // Assuming x = 1 -> Assuming density = density_g

    enthalpy = helmholtz::h(temperature, density_g);
    entropy = helmholtz::s(temperature, density_g);
    internalEnergy = helmholtz::u(temperature, density_g);
    isochoricHeatCapacity = helmholtz::c_v(temperature, density_g);
    isobaricHeatCapacity = helmholtz::c_p(temperature, density_g);
    gibbsFreeEnergy = helmholtz::g(temperature, density_g);  //dimensionless
    helmholtzFreeEnergy = helmholtz::f(temperature, density_g);  //dimensionless
    soundSpeed = helmholtz::w(temperature, density_g);
    quality = 1;
    saturated = true;

    return;
}

// From steamtable.cpp
#include "msys.h" // It's included through headers everywhere

// Function calling setState_Tsat() when error occurs
void STable1::computeTable()
{
    do
    {                   //Originally a separate `` was used, but the logic will work by modifying 'low'
        state->setState_Tsat(low);
        /* A bunch of string manipulation */

        // We want to print the top of the range, even if it doesn't fall in the normal interval.
        if (low + pInterval >= high)
        {
            state->setState_Tsat(high);
            /* A bunch of string manipulation */
        }
        low += pInterval;
    }
    while (low < high);
    // Done adding data. Now for column formatting

    return;
}

// From main.cpp
#include "msys.h"

int main()
{
    /* Do some user input handling */

         if (boost::algorithm::to_lower_copy(msys_string) == "base")
        MSys::setMSys(UnitSystem::base);
    else if (boost::algorithm::to_upper_copy(msys_string) == "SI")
        MSys::setMSys(UnitSystem::SI);
    else if (boost::algorithm::to_upper_copy(msys_string) == "CGSC")
        MSys::setMSys(UnitSystem::CGSC);
    else if (boost::algorithm::to_upper_copy(msys_string) == "CGSK")
        MSys::setMSys(UnitSystem::CGSK);
    else if (boost::algorithm::to_upper_copy(msys_string) == "MTSC")
        MSys::setMSys(UnitSystem::MTSC);
    else if (boost::algorithm::to_upper_copy(msys_string) == "MTSK")
        MSys::setMSys(UnitSystem::MTSK);
    else if (boost::algorithm::to_upper_copy(msys_string) == "FPSF")
        MSys::setMSys(UnitSystem::FPSF);
    else if (boost::algorithm::to_upper_copy(msys_string) == "FPSR")
        MSys::setMSys(UnitSystem::FPSR);
    else
        /* Error handling */

    /* Start table setup */
    table = new STable1;

    table->precision(std::abs(std::stoi(prec_string)));
    table->setpInterval(std::stod(prange[0]), std::stod(prange[1]), std::abs(std::stod(prange[2])));
    
    // No problems getting the corret values from function calls here
    std::cout << "Calculating Table 1 in the Temperature Range from " << prange[0] << MSys::temperatureUnit()
                << " to " << prange[1] << MSys::temperatureUnit() << " in " << prange[2] << MSys::temperatureUnit()
                << " increments. Please wait..." << std::endl;

    // This computeTable() is the first in the chain of function calls that produce the error
    table->generateTable();

    return 0; // Eventually
}
Last edited on
Why is that anonymous namespace in a header? Normally anonymous namespaces are located within source files.

1
2
3
    // Function in which the system is wrong
    template <typename T>
    T densityToBase(T input)


Where are you trying to access a variable named US in that function?

And what exactly do you mean by "the system is wrong"?

The anonymous namespace is in the header so that the function templates can utilize it.

The access is in the body of the function. The function looks at the global variable US (the one with which I'm having problems) to determine which measurement system I'm using.

When I say "the system is wrong," I mean at that point in program execution, the problem described in the first post has occurred. "The system" is the measurement system I have assigned earlier in the program execution, i.e. the value of US.
The anonymous namespace is in the header so that the function templates can utilize it

The point of anonymous namespaces to uniformly give types, variables, and functions internal linkage. If this library was header-only, the anonymous namespace is necessary to avoid ODR violations, but the side effect is that there's one MSys::US per translation unit that includes it.

You'd want to declare MSys::US with extern and define it once elsewhere, or declare the variable inline (in C++17).
Last edited on
1
2
3
4
5
6
    num_t psat(num_t T, num_t& rho_f, num_t& rho_g)
...
        rho_f = rho_tf, rho_g = rho_tg;
...
        reduce(MSys::tempToBase(T), MSys::densityToBase(rho_f), delta_f, tau);
        reduce(MSys::tempToBase(T), MSys::densityToBase(rho_g), delta_g, tau);


That's exactly the same as
1
2
3
4
    num_t psat(num_t T, num_t& rho_f, num_t& rho_g)
...
        reduce(MSys::tempToBase(T), MSys::densityToBase(rho_tf), delta_f, tau);
        reduce(MSys::tempToBase(T), MSys::densityToBase(rho_tg), delta_g, tau);

What units are your rho_tf and rho_tg in? The user's units, or the base (SI?) units? Where are they specified? If they are actually in SI units then the supplementary equations would be
1
2
        reduce(MSys::tempToBase(T), rho_tf, delta_f, tau);
        reduce(MSys::tempToBase(T), rho_tg, delta_g, tau);



Also, what does MSys::tempToBase(T) look like?


it suddenly behaves as if the default measurement system is selected.
What exactly happens?


and then convert the returns back to the user's chosen unit system
Where do you do that?
Last edited on
You'd want to declare it with extern, or declare the variable inline (in C++17)

I've added the extern keyword and have tried moving the declaration into the .cpp file. At the switch(US) statement in my template functions, I'm now getting:
error: undefined reference to 'MSys::(anonymous namespace)::US'

Code is structured like this now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// msys.h
enum class UnitSystem : char
{
	base, SI, FPSF, FPSR, CGSC, CGSK, MTSC, MTSK
};

namespace MSys
{
    namespace // Anonymous namespace to protect the unit system
    {
        extern UnitSystem US;
    }

    // Set & get the system
    void setMSys(UnitSystem US_);
    UnitSystem getMSys();

    template <typename T>
    T densityToBase(T input)
    {
        static_assert(std::is_arithmetic<T>::value, "Type must be numeric");

        switch (US)
        {
            case UnitSystem::base:
            case UnitSystem::SI:
                return input;
                break;
            case UnitSystem::MTSC:
            case UnitSystem::MTSK:
                return input / 1e3;
                break;
            case UnitSystem::CGSK:
            case UnitSystem::CGSC:
                return input / 1e-3; // Convert g / cm^3 -> kg / m^3
                break;
            case UnitSystem::FPSR:
            case UnitSystem::FPSF:
                return input / 62.427961e-3; // Covert lbm / ft^3 -> kg / m^3
                break;
        }
    }

    /* Other template functions */
}

// msys.cpp
#include "msys.h"

namespace MSys
{
    namespace // Anonymous namespace to protect the unit system
    {
        UnitSystem US = UnitSystem::base;
    }
    // This series of functions returns the string associated with the chose unit system

    std::string massUnit()
    {
        switch (US)
        {
            case UnitSystem::base:
            case UnitSystem::SI:
                return "kg";
                break;
            case UnitSystem::CGSC:
            case UnitSystem::CGSK:
                return "g";
                break;
            case UnitSystem::MTSC:
            case UnitSystem::MTSK:
                return "tonne";
                break;
            case UnitSystem::FPSF:
            case UnitSystem::FPSR:
                return "lbm";
                break;
        }
    }

    /* Other normal functions */
}


Quite frankly, I've never worked with anonymous namespaces or extern declarations before.
My original plan was to build a static class, but while researching it, I ran across a strong argument to use namespaces instead, utilizing anonymous namespaces in place of private data members. Should I simply revert this to a static class?
I've condensed it to (only) about 225 lines of the relevant code. Applicable notes are on lines 15, 51, 96, 110, 140, 164, 218, & 223.


By the way a small complete program that compiles and illustrates the problem will be more helpful than a bunch of seemingly unrelated code.

Having an anonymous namespace in a header really tends to defeat the purpose of an anonymous namespace, IMO. Normally you use an anonymous namespace to make particular contents in a source file "private" to that source file and not visible anywhere else but that source file. Also, again IMO, having an anonymous namespace in a source file that contains a big portion of your source code is of very limited use.



Like I said, the purpose of anonymous namespaces is to give stuff internal linkage. Totally opposed to extern. Everything declared inside an anonymous namespace has internal linkage.

You should give the namespace a name - the convention for such private namespaces is a nested namespace named internal or detail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace MSys {
  // etc.
  namespace detail {
     extern UnitSystem US; // declared with external linkage
  }

  namespace {
    extern int foo; // declared with internal linkage
  }
}

// in one translation unit
namespace MSys { namespace detail {
  UnitSystem US{};
}}
Last edited on
Thanks for all the info. I was thoroughly unfamiliar with the topic. I appreciate the feedback.

My premise originated from the second answer given in here: https://stackoverflow.com/questions/9321/how-do-you-create-a-static-class-in-c
Glad I could help. I hope I didn't seem annoyed.
Topic archived. No new replies allowed.