Question about global variables

SOLVED

I don't understand why this code behaves the way it does. I have a constant pointer included in two files which takes different values in each.

header.h

void file1();

int* const global_ptr = new int;

file1.cpp

#include "header.h"

#include <cstdio>

void file1() {
	printf("File 1 ptr: %p\n", global_ptr);
}

file2.cpp

#include "header.h"

#include <cstdio>

void file2() {
	printf("File 2 ptr: %p\n", global_ptr);
}

int main() {
	file1();
	file2();
}

I know the two files have their own copies of global_ptr since it is const but when I run the code you can see they point to different locations:

File 1 ptr: 0x2503030
File 2 ptr: 0x2503010

My best guess is that new gets called separately for each file but your insight (and any ways to work around this) is appreciated.

Posted Jun 22, 2016, 3:50 PM UTC · edited

211
kiiro•

Not sure what you're end goal here is, apparently a work around would look like this, but you shouldn't do this to begin with. You should declare ptr locally (inside your main using auto* ptr = make_unique<int>()) and then rewrite your functions to take a const int* parameter and call them using file(ptr.get()). This will properly delete the pointer automatically.

If C++11 or boost isn't an option you can do the same using new and delete inside main.

2
17b29a•

My best guess is that new gets called separately for each file but your insight (and any ways to work around this) is appreciated.

That is correct. Both file1.cpp and file2.cpp define their own global_ptr, and both initialize them with a new int from the heap.

The simplest solution is to declare global_ptr as extern in the header, and only define it once, i.e.

extern int* const global_ptr;

in the header, and

int* const global_ptr = new int;

in either file1.cpp or file2.cpp

2
MerionesOP•

That works perfectly! Thanks

1
[deleted]•

[deleted]

1
EraZ3712•

The documentation for printf states this for %p:

writes an implementation defined character sequence defining a pointer.

What you are printing is not the value of the int pointed to by global_ptr, but an implementation-defined representation of the value of global_ptr itself. In other words, you're printing an address. This is hinted at by the output being given in hexadecimal, a convention when referring to addresses.

You should be dereferencing global_ptr and using the %i or %d format specifiers instead:

printf("File 1 ptr: %i\n", *global_ptr);

The result should be, and is, the expected value of 0, the default of a default-initialized integer. However, you are correct in saying that the global_ptr referenced in File1 and File2 are not the same, but it is not because of the const qualifier. It is because of the lack of include guards: you are in essence duplicating the code in header.h in file1.cpp and file2.cpp, no different from if you had copy-pasted them in place of #include "header.h". T̶o̶ ̶m̶a̶k̶e̶ ̶s̶u̶r̶e̶ ̶t̶h̶a̶t̶ ̶t̶h̶e̶r̶e̶ ̶e̶x̶i̶s̶t̶s̶ ̶o̶n̶l̶y̶ ̶o̶n̶e̶ ̶i̶n̶s̶t̶a̶n̶c̶e̶ ̶o̶f̶ ̶t̶h̶e̶ ̶c̶o̶n̶t̶e̶n̶t̶s̶ ̶o̶f̶ ̶̶h̶e̶a̶d̶e̶r̶.̶h̶̶ ̶a̶c̶r̶o̶s̶s̶ ̶a̶l̶l̶ ̶f̶i̶l̶e̶s̶ ̶w̶h̶i̶c̶h̶ ̶h̶a̶v̶e̶ ̶i̶n̶c̶l̶u̶d̶e̶d̶ ̶i̶t̶ ̶u̶p̶o̶n̶ ̶c̶o̶m̶p̶i̶l̶a̶t̶i̶o̶n̶,̶ ̶y̶o̶u̶ ̶m̶u̶s̶t̶ ̶u̶s̶e̶ ̶i̶n̶c̶l̶u̶d̶e̶ ̶g̶u̶a̶r̶d̶s̶.̶ Edit: Include guards prevent multiple instances per compilation unit, not across all files.

As an additional note, (Edit: non-const) global variables should be avoided if possible, doubly so if they are raw pointers. Raw pointers do not clean up after themselves, so a delete global_ptr; statement at the end of main() is (very) recommended. You should either move the pointer into a scope or use a std::unique_ptr<int>.

1
MerionesOP•

I added include guards but I still get two different addresses.

What I am trying to to is write a tile-based game where the map is an array of pointers to different instances of my Tile class. I wanted to have these instances as global const variables in a namespace but they have a field SDL_Texture* texture; that I have to initialize at runtime. It seems there is no way to modify a global variable like that and have the change affect all files but I thought I could make the global variable a constant pointer instead so all changes would be directed to the same bit of memory. Unfortunately it seems this is not correct.

1
EraZ3712•

Whoops, my mistake. Include guards prevent multiple inclusions in a single compilation unit, not across all compilation units. So yes, even with include guards, each compilation unit would have it's own global_ptr, so the behavior you're seeing is correct.

If I understood correctly, you want a 2-dimensional array of a class Tile. Tile contains a member variable of type SDL_Texture* which must be initialized at runtime.

It sounds like you move the Tile matrix into a class Map which initializes the matrix appropriately upon construction. This would also guarantees there are no "duplicate" instances due to declaring the global variable in a header; you can pass the map around instead by reference. Here's an example of what I mean, using the information you've given me:

#include <vector>

struct SDL_Texture { };

class Tile {
  SDL_Texture* texture;

public:
  Tile() : texture(new SDL_Texture()) { }
  ~Tile() { delete texture; }
};

class Map {
  std::vector<Tile*> m_tiles;
  std::size_t        m_width;
  std::size_t        m_height;

public:
  Map(std::size_t width, std::size_t height)
  : m_tiles(width * height), m_width(width), m_height(height) {
    for (Tile* tile : m_tiles) tile = new Tile();
  }

  ~Map() { for (Tile* tile : m_tiles) delete tile; }
};

int main() {
  Map map(10, 20);
}
2
MerionesOP•

I was trying to avoid creating a new Tile instance for every map location, since I only have a few terrain types, so my Map constructor would look like

Map(std::size_t width, std::size_t height)
    : m_tiles(width * height)
    , m_width(width)
    , m_height(height) {
    for (Tile* tile : m_tiles) {
        if (someCondition()) {
            tile = Tiles::dirt;
        } else {
            tile = Tiles::empty;
        }
    }
}
1
EraZ3712•

Ah, that's definitely a better way of going about it!

I hope this has helped somewhat!

2
17b29a•

It seems there is no way to modify a global variable like that and have the change affect all files

I think the issue you're running into is that global variables with top-level const have internal linkage by default, i.e. they're local to the file they're defined in, the same way local variables are local to the function they're defined in. Each file will end up with a different, independent instance.

You don't need to use pointers. You can just have something like extern Map g_Map; in the header and Map g_Map; in some cpp file.

Or, if you want to take advantage of internal linkage, since that's the "clean" way to do global variables, you can have something like const Map& GetMap(); in the header and static Map g_Map; const Map& GetMap() { return g_Map; } in some cpp file.

2
[deleted]•

[deleted]

1
[deleted]•

[deleted]

1