2

I have this code, and I noticed that when I define my arithmethicStruct arithmethicArray[] array without the static attribute, I get a compile error of multiple definition. With the static attribute, it compiles. Can I get an explanation of how this global static variables behave at compile time and why the error is suppressed, and the difference between a normal global one? I am a bit confused when using multiple files. Also a clarification of extern would be helpful.

//main.cpp

#include "functionPointers.h"
#include <iostream>

int main() {
  int a = getNumber();
  char op = getOperatrion();
  int b = getNumber();

  arithmethicFunc func = getArithmeticFunct(op);

  std::cout << a << " " << op << " " << b << " = " << func(a, b) << "\n";

  return 0;
}

//functionPointers.h

int getNumber();
char getOperatrion();

typedef int (*arithmethicFunc)(int, int&);

int sum(int a, int&b);
int diff(int a, int&b);
int mult(int a, int&b);
int div(int a, int&b);

arithmethicFunc getArithmeticFunct(char op);

struct arithmethicStruct {
  arithmethicFunc func;
  char op;
};

//here is the question, with static it compiles
arithmethicStruct arithmethicArray[] {
  {sum, '+'},
  {diff, '-'},
  {mult, '*'},
  {div, '/'}
};

//functionpointers.cpp

#include "functionPointers.h"
#include <iostream>

int getNumber() {
  int a;
  std::cout << "Enter a number : ";
  std::cin >> a;
  return  a;
}

char getOperatrion() {
  char a;
  std::cout << "Enter an operation (+, -, * , /) : ";
  std::cin >> a;
  return  a;
}

int sum(int a, int&b) { return a+b; }
int diff(int a, int&b) { return a-b; }
int mult(int a, int&b) { return a*b; }
int div(int a, int&b) { return a/b; }

arithmethicFunc getArithmeticFunct(char op) {
  switch (op) {
    case '+': return sum;
    case '-': return diff;
    case '*': return mult;
    case '/': return div;
  }
}
Evg
  • 25,259
  • 5
  • 41
  • 83
learning_dude
  • 1,030
  • 1
  • 5
  • 10
  • It is because you are defining the variable in h file and you must don't do this. Each time you write #include "functionpointers.h" in a cpp, the compiler creates one variable, and generates the compiler error. If you define this variable as static, the compiler only create one instance and uses allways the same. You must to instanciate variables in cpp files, not in h files. But doing this, the variables defined in a cpp are not visible in another, so you need to use extern modifier in the cpp where you need to use it. One cpp define the variable without extern and the other ones with it. – SuperG280 Nov 06 '19 at 07:51

3 Answers3

1

The error you get is not exactly a compilation error but a link error.
This means that every translation unit (ie .c file with all the .h it includes) you compile individually is considered correct by the compiler but when it comes to linking them (putting the resulting .o files together to produce the executable file as a whole) the linker finds some inconsistencies between these various object files (.o files).

I will try to reproduce this on a simpler example (in C to be general because it is similar in C and C++).
my_global.h

#ifndef MY_GLOBAL_H
#define MY_GLOBAL_H

static int my_global_with_static[]={10, 20, 30, 40};

extern int my_global_with_extern[4];

void
show_array(const char *title,
           const int *array,
           int count);

void
another_function(void);

#endif

my_global.c


#include "my_global.h"

#include <stdio.h>

int my_global_with_extern[4]={1, 2, 3, 4};

void
show_array(const char *title,
           const int *array,
           int count)
{
  printf("%s:\n", title);
  printf("  at %p:", (void *)array);
  for(int i=0; i<count; ++i)
  {
    printf("  %d", array[i]);
  }
  printf("\n");
}

void
another_function(void)
{
  show_array("my_global_with_static from another translation unit",
             my_global_with_static, 4);
  show_array("my_global_with_extern from another translation unit",
             my_global_with_extern, 4);
}

prog.c


#include "my_global.h"

#include <stdio.h>

int
main(void)
{
  show_array("my_global_with_static from main translation unit",
             my_global_with_static, 4);
  show_array("my_global_with_extern from main translation unit",
             my_global_with_extern, 4);
  another_function();
  return 0;
}

When running this program, I obtain this result

my_global_with_static from main translation unit:
  at 0x5630507e10a0:  10  20  30  40
my_global_with_extern from main translation unit:
  at 0x5630507e1200:  1  2  3  4
my_global_with_static from another translation unit:
  at 0x5630507e11c0:  10  20  30  40
my_global_with_extern from another translation unit:
  at 0x5630507e1200:  1  2  3  4

We can see that my_global_with_extern is the exact same array when considered from the main translation unit or from another one; indeed not only the values are the same but this array is visible at the same address (0x5630507e1200 during this run).

On the other hand, my_global_with_static looks the same in both translation units (the values are identical) but consists actually in two distinct arrays with their own memory location in each translation unit (0x5630507e10a0 and 0x5630507e11c0 in this run).

The extern keyword means that we are only declaring the variable.
It is a promise made to the compiler reading this line that somewhere else this variable is defined so we can express some code that uses it (exactly like a function declaration: the prototype but not the code).
Thus in one and only one translation unit we must provide the only definition of this variable (in my_global.c here).

On the other hand the static keyword means that the following definition should be considered local to the current translation unit.
The symbol is not exported so it will not collide with another symbol having the same name in another translation unit.
The consequence is that if this static definition appears in a .h file and if this file is included in many translation unit, then each translation unit have its own definition of this variable (thus the different addresses reported in the example).

prog-fh
  • 13,492
  • 1
  • 15
  • 30
1

Simple case

To make things easier, your problem could be simplified as the following:

Now there is a header file named header.h:

// header.h
int a = 1;

Here a variable a is DEFINED.

Now let us have the launcher code named main.cpp:

// main.cpp
#include "header.h"

int main() {return 0;}

Whenever these lines are being compiled, the C(C++) compiler COPIES the content from header.h file into the following:

// main.cpp after pre-compile stage
int a = 1;     // <- this line is from header.h

int main() {return 0;}

The definition works in this case since there are no other source files. Let us make it more complicated by adding a new source code file header_impl.cpp

// header_impl.cpp: original source code
#include "header.h"

int inc_a() { a++; }

Then after expansion, header_impl.cpp becomes:

// header_impl.cpp after pre-compile stage
int a = 1;     // <- this line is from header.h

int inc_a() { a++; }

When the two source code files are compiled EACH, there are no problems yet. The problem occurs when the compiler tries to link two object files together into ONE executable file. The variable with the same name a is defined twice.

Why STATIC works

In the upper case, if we modify the definition of a in header.h file with static:

// header.h
static int a = 1;

The after pre-compile stage, the other two files becomes:

// main.cpp after pre-compile stage
static int a = 1;     // <- this line is from header.h

int main() {return 0;}

and:

// header_impl.cpp after pre-compile stage
static int a = 1;     // <- this line is from header.h

int inc_a() { a++; }

So why the compiler does not throw a link error anymore? The answer is, static means this variable or function is invisible to other source code files. The scope of the variable a definition is restricted to each file only. That is, the two as are totally DIFFERENT, even with the same name.

Why EXTERN works and how it should be used

I guess you were intended to use a global variable in the header file. I think the best choice is to use the extern keyword. The three source code files are listed below:

// header.h
extern int a; // only declaration
// main.cpp
#include "header.h"

int main() {return 0;}
// header_impl.cpp
#include "header.h"

int a = 1; // definition here

int inc_a() { a++; }

After pre-compilation stage, these two source code files (header files are not compiled into object files) are as the following:

// main.cpp after pre-compile stage
extern int a;     // <- this line is from header.h

int main() {return 0;}
// header_impl.cpp after pre-compile stage
extern int a;     // <- this line is from header.h

int a = 1; // definition here

int inc_a() { a++; }

Here the compiler knows that there is an external variable a when compiling the main source code file, and the definition is found when the two object files are linked together.

The PS

1. A little syntax error

There's a syntax error at where you thought was a problem: you need an =.

arithmethicStruct arithmethicArray[] = {
 {sum, '+'},
 {diff, '-'},
 {mult, '*'},
 {div, '/'}
};

2. Preventing multiple includes with a macro

It's better to wrap the content in each header file with its own macro like this:

#ifndef _HEADER_H_
#define _HEADER_H_
extern int a;
#endif

Here I use _HEADER_H_, but the macro name could be arbitrary. It is useful if multiple header files are including each other in a complicated way.

Daniel Shao
  • 137
  • 1
  • 8
  • Thank you so much..you made clear a lot of questions that I had. Just FYI, c++ std-11 allows not to use the "=" when defining the structure arrax – learning_dude Nov 06 '19 at 15:09
0

the static variable declaration tells the compiler not to put this variable in object symbol table this allows to have multiple variables with the same name in different cpp files. this means that these variables not visible to other cpp files. in your case you declare and initialize the variable in .h which included in to .cpp this will generate two different variables with same name makes the complier fails. when you put static the compiler also create two different variables but hide them by not generate symbol. in order to correct this declare variables in .cpp files and use functions to retrieve there values from other cpp files.

Ahmed Anter
  • 650
  • 4
  • 13