Inline constraint not respected

Modifiable static variable is modified in nonstatic inline function

Description

This defect occurs when you refer to a file scope modifiable static variable or define a local modifiable static variable in a nonstatic inlined function. The checker considers a variable as modifiable if it is not const-qualified.

For instance, var is a modifiable static variable defined in an inline function func. g_step is a file scope modifiable static variable referred to in the same inlined function.

static int g_step;
inline void func (void) {
   static int var = 0;
   var += g_step;
}

Risk

When you modify a static variable in multiple function calls, you expect to modify the same variable in each call. For instance, each time you call func, the same instance of var1 is incremented but a separate instance of var2 is incremented.

void func(void) {
   static var1 = 0;
   var2 = 0;
   var1++;
   var2++;
}

If a function has an inlined and non-inlined definition (in separate files), when you call the function, the C standard allows compilers to use either the inlined or the non-inlined form (see ISO®/IEC 9899:2011, sec. 6.7.4). If your compiler uses an inlined definition in one call and the non-inlined definition in another, you are no longer modifying the same variable in both calls. This behavior defies the expectations from a static variable.

Fix

Use one of these fixes:

  • If you do not intend to modify the variable, declare it as const.

    If you do not modify the variable, there is no question of unexpected modification.

  • Make the variable non-static. Remove the static qualifier from the declaration.

    If the variable is defined in the function, it becomes a regular local variable. If defined at file scope, it becomes an extern variable. Make sure that this change in behavior is what you intend.

  • Make the function static. Add a static qualifier to the function definition.

    If you make the function static, the file with the inlined definition always uses the inlined definition when the function is called. Other files use another definition of the function. The question of which function definition gets used is not left to the compiler.

Examples

expand all

/* file1. c  : contains inline definition of get_random()*/

inline unsigned int get_random(void) 
{

    static unsigned int m_z = 0xdeadbeef; 
    static unsigned int m_w = 0xbaddecaf; 

    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16); 
    m_w = 18000 * (m_w & 65535) + (m_w >> 16); 
    return (m_z << 16) + m_w;   
}


int call_get_random(void)
{
    unsigned int rand_no;
    int ii;
    for (ii = 0; ii < 100; ii++) {
         rand_no = get_random();
    }
    rand_no = get_random();
    return 0;
}
/* file2. c  : contains external definition of get_random()*/

extern unsigned int get_random(void)
{
    /* Initialize seeds */
    static unsigned int m_z = 0xdeadbeef;
    static unsigned int m_w = 0xbaddecaf;
    
    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16);
    m_w = 18000 * (m_w & 65535) + (m_w >> 16);
    return (m_z << 16) + m_w;
}

In this example, get_random() has an inline definition in file1.c and an external definition in file2.c. When get_random is called in file1.c, compilers are free to choose whether to use the inline or the external definition.

Depending on the definition used, you might or might not modify the version of m_z and m_w in the inlined version of get_random(). This behavior contradicts the usual expectations from a static variable. When you call get_random(), you expect to always modify the same m_z and m_w.

Correction — Make Inlined Function Static

One possible correction is to make the inlined get_random() static. Irrespective of your compiler, calls to get_random() in file1.c then use the inlined definition. Calls to get_random() in other files use the external definition. This fix removes the ambiguity about which definition is used and whether the static variables in that definition are modified.

/* file1. c  : contains inline definition of get_random()*/

static inline unsigned int get_random(void) 
{

    static unsigned int m_z = 0xdeadbeef; 
    static unsigned int m_w = 0xbaddecaf; 

    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16); 
    m_w = 18000 * (m_w & 65535) + (m_w >> 16); 
    return (m_z << 16) + m_w;   
}


int call_get_random(void)
{
    unsigned int rand_no;
    int ii;
    for (ii = 0; ii < 100; ii++) {
         rand_no = get_random();
    }
    rand_no = get_random();
    return 0;
}
/* file2. c  : contains external definition of get_random()*/

extern unsigned int get_random(void)
{
    /* Initialize seeds */
    static unsigned int m_z = 0xdeadbeef;
    static unsigned int m_w = 0xbaddecaf;
    
    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16);
    m_w = 18000 * (m_w & 65535) + (m_w >> 16);
    return (m_z << 16) + m_w;
}

Result Information

Group: Programming
Language: C | C++
Default: On for handwritten code, off for generated code
Command-Line Syntax: INLINE_CONSTRAINT_NOT_RESPECTED
Impact: Medium
Introduced in R2018a