通过函数访问全局变量 - 跨命名空间的不同实例

时间:2015-11-12 12:59:48

标签: c++11 namespaces global-variables

我正在完成大学任务。一般的想法是创建一个用c ++编写的库,并在C和C ++头文件中公开它的内容。我们应该有一个全局变量,其中包含存储电话号码更改的字典字典(unordered_map),并使用标题中公开的函数对其进行修改。换句话说,我们有unordered_map<id, unordered_map<old_phone_nr, new_phone_number>>。这是我的C ++标题

namespace jnp1 {

extern "C" {
extern size_t TEL_NUM_MAX_LEN;

unsigned long maptel_create();
// create a dictionary of telephone number changes (old->new) and return its ID

void maptel_delete(unsigned long id);
// delete the dictionary ID

void maptel_insert(unsigned long id, char const *tel_src, char const *tel_dst);
// insert change into dictionary ID

void maptel_erase(unsigned long id, char const *tel_src);
// erase change from dictionary ID

void maptel_transform(unsigned long id, char const *tel_src, char *tel_dst, size_t len);
// follow chain of changes (nr1 -> nr2 -> ... -> final_number) and return final number
}
}

我们有一个类似的C头。 我们的库必须符合外部测试 - C版本:

#include <assert.h>
#include <string.h>
#include "maptel.h" // our library C header

static const char t112[] = "112";
static const char t997[] = "997";

int main() {
  unsigned long id;
  char tel[TEL_NUM_MAX_LEN + 1]; /* +1 for terminal zero */

  id = maptel_create();
  maptel_insert(id, t112, t997);
  maptel_transform(id, t112, tel, TEL_NUM_MAX_LEN + 1);
  assert(strcmp(tel, t997) == 0);
return 0;

}

这没有任何问题。但是,C ++测试用例使用未命名的命名空间,如

#include <cassert>
#include <cstddef>
#include <cstring>
#include "cmaptel" // our library C++ header

namespace {

  unsigned long testuj() {
    unsigned long id;

    id = ::jnp1::maptel_create();
    ::jnp1::maptel_insert(id, "997", "112");

    return id;
  }

  unsigned long id = testuj();

} // anonymous namespace

int main() {
  char tel[::jnp1::TEL_NUM_MAX_LEN + 1];

  ::jnp1::maptel_transform(id, "997", tel, ::jnp1::TEL_NUM_MAX_LEN + 1); // here it breaks
  assert(::std::strcmp(tel, "112") == 0);
  ::jnp1::maptel_delete(id);
}

在第一次从命名空间外部访问时,我们在函数内部进行断言,因为id不在字典中。换句话说,匿名命名空间maptel_create()在变量的某个其他实例中创建id字典而不是maptel_transform()中的void main(){}尝试访问它。我们无法对此问题进行故障排除,但是将全局unordered_map<unsigned long, unordered_map<string, string>>移动到堆(我们使变量成为指针,而不是对象,并在第一次调用maptel_create()时分配它)可以正常工作 - 除了规范任务无法保证我们可以解除分配,导致内存泄漏。

我正在寻找这种现象的帮助/解释。请不要告诉我使用类而不是全局变量 - 虽然这显然是正确的设计选择,但我们必须遵守一个规范。下面我展示了创建/擦除功能的堆栈和堆栈版本(在这个问题上擦除与转换相同,它只是更短,所以更好的例子)

堆栈:

#include "cmaptel"

typedef std::unordered_map<std::string, std::string> tel_book;
std::unordered_map<unsigned long, tel_book> books;

unsigned long id_counter;

extern "C" {
    size_t TEL_NUM_MAX_LEN = 22;

    unsigned long jnp1::maptel_create()
    {
        books.reserve(1);
        // without this for some reason we would get float arithmetic exception right here

        books[id_counter] = tel_book();
        return id_counter++;
    }

    void jnp1::maptel_erase(unsigned long id, char const *tel_src)
    {
        assert(books.count(id)); // this is the assertion that breaks
        std::string src(tel_src);
        int result = books[id].erase(src);

    }
}

堆:

#include "cmaptel"

typedef std::unordered_map<std::string, std::string> tel_book;
std::unordered_map<unsigned long, tel_book> *books;

unsigned long id_counter;

extern "C" {
    size_t TEL_NUM_MAX_LEN = 22;

    unsigned long jnp1::maptel_create()
    {
        if (books == NULL) books = new std::unordered_map<unsigned long, tel_book>();
        (*books)[id_counter] = tel_book();
        return id_counter++;
    }

    void jnp1::maptel_erase(unsigned long id, char const *tel_src)
    {
        assert(books->count(id));
        std::string src(tel_src);
        int result = (*books)[id].erase(src);

    }
}

1 个答案:

答案 0 :(得分:1)

多个翻译单元未指定全局变量的初始化顺序。问题是id(因此对maptel_insert的调用)正在books之前进行初始化。由于maptel_insert正在对未初始化的对象进行操作,因此这是未定义的行为。

您已找到一种解决方法:按需动态分配地图,从而确保在使用前分配地图。另一个微小的变化是使用函数范围的静态,因为它们具有保证在使用前初始化它们的规则:

std::unordered_map<unsigned long, tel_book>& books()
{
    static std::unordered_map<unsigned long, tel_book> instance;
    return instance;
}

C++11 also guarantees this initialization is done in a thread-safe manner.