Почему c ++ помещает переменные с одинаковыми именами, определенные в отдельных модулях, в один и тот же адрес в памяти? - PullRequest
3 голосов
/ 01 января 2012

Давайте возьмем заголовочный файл var.h

#include <iostream>

class var
  {public:
      var () {std::cout << "Creating var at " << this << std::endl; }
      ~var () {std::cout << "Deleting var at " << this << std::endl; }
  };

и два исходных файла: первый lib.cpp

#include "var.h"
var A;

и второй app.cpp

#include "var.h"

var A;

int main ()
  {return 0;
  }

затем, если я попытаюсь скомпилировать их

g++ -c app.cpp
g++ -c lib.cpp
g++ -o app app.o lib.o

компоновщик вернет ошибку с множественными переменными.Но если я скомпилирую его в разделяемую библиотеку + главное приложение

g++ -fPIC -c lib.cpp
g++ --shared -o liblib.so lib.o
g++ -fPIC -c app.cpp
g++ -o app -llib -L . app.o

, он будет ссылаться без ошибок.Однако программа не работает должным образом:

./app
Creating var at 0x6013c0
Creating var at 0x6013c0
Deleting var at 0x6013c0
Deleting var at 0x6013c0

, поэтому разные переменные были созданы по одному адресу памяти!Это может привести к серьезным проблемам, например, в случае, когда библиотека и приложение ожидают, что они будут иметь разные значения (в данном случае значения полей объекта).

, если class var сделать выделение памяти / удаление valgrind предупреждаето доступе к памяти в недавно удаленном блоке.

Да, я знаю, что мог бы поставить static var A; вместо var A;, и оба способа компиляции будут работать правильно.Мой вопрос: почему нельзя использовать одноименные переменные (или даже функции?) В разных библиотеках?Создатели библиотеки могут ничего не знать об именах, которые используют друг друга, и не должны предупреждаться об использовании static.Почему GNU связанный не предупреждает об этом конфликте?

И, кстати, может dlload поставить в ту же проблему?

UPD.Спасибо всем за объяснение пространств имен и extern, я понимаю, почему одни и те же символы помещаются в один и тот же адрес памяти, но все еще не могу понять, почему не отображается ошибка связывания или даже предупреждение о дважды определенной переменной, а во втором случае генерируется неправильный код.

Ответы [ 5 ]

5 голосов
/ 01 января 2012

Мой вопрос: почему нельзя использовать переменные с одинаковыми именами (или даже функции?) в разных библиотеках?

Можно. Вы упускаете то, что декларации

var A;

не определяет символ A для использования в библиотеке. Они определяют символ для экспорта, чтобы любой другой модуль компиляции мог ссылаться на него!

например. если в app.cpp вы объявили

extern var A;

это означало бы объявить "A - это переменная типа var, которую какой-то другой модуль компиляции собирается определить и экспортировать" - с этой модификацией в вашей установке это будет app.cpp явно запрашивать использование объект с именем A, который lib.cpp экспортирован.

Проблема с вашей настройкой заключается в том, что у вас есть два разных модуля компиляции, которые пытаются экспортировать один и тот же символ A, что приводит к конфликту.

Why GNU linked doesn't warn about this conflict?

Поскольку GNU не может знать, что вы хотели, чтобы A была переменной, частной для вашего модуля компиляции, если вы не скажете GNU, что оно должно быть частным для вашего модуля компиляции. Вот что означает static в этом контексте.

4 голосов
/ 01 января 2012

Непонятно, спрашиваете ли вы, должно ли это произойти или каково обоснование.

Во-первых, это обязательное поведение. Согласно «правилу одного определения», раздел 3.2 стандарта C ++, если несколько единиц перевода содержат идентичные определения (и некоторые другие требования выполняются), то программа должна вести себя так, как если бы существовало одно определение. В любом другом случае, когда существует несколько определений, поведение не определено.

Если вы спрашиваете, каково обоснование этого правила, оно обычно то, что вы хотите. Ваш компилятор может иметь возможность предупреждать, если несколько определений не помечены extern.

2 голосов
/ 01 января 2012

Разные библиотеки должны иметь по-разному глобальные переменные и глобальные функции, в противном случае случаются очень неприятные вещи (например, когда dlopen - это несколько раз ...).

Обычно библиотеки с хорошим поведением используют общий префикс (например, gtk) в C или пространство имен в C ++.

И библиотеки должны минимизировать глобальное состояние (в C ++ это, вероятно, статические данныевнутри классов).

Вы также можете использовать visibility атрибут функции , принятый GCC.

1 голос
/ 05 января 2012

Символы со связью extern (в данном случае это значение по умолчанию) видны другим единицам перевода.Это позволяет интерфейсам между исходными файлами, библиотеками и т. Д.

Наличие или отсутствие определения не влияет на доступ к объекту.Программист отвечает за организацию объявлений и определений таким образом, чтобы объект всегда объявлялся перед использованием и всегда определялся ровно один раз (правило одного определения).

Лучшее решение - поместить частные глобальные переменные в безымянные пространства имен, чтобыопределения, которые выглядят одинаково, могут быть разными.

lib.cpp

#include "var.h"
namespace { // unnamed namespace
    var A; // object inaccessible to other translation units
}

app.cpp

#include "var.h"

namespace { // different unnamed namespace
    var A; // different object
}

int main ()
  {return 0;}
0 голосов
/ 01 января 2012

Несколько упрощенный ответ: «библиотеки» - это деталь реализации. Все объектные файлы объединяются (связываются) в один блок (исполняемый файл) до до выполнения. После создания ссылок больше не осталось никаких следов библиотек, исходных файлов и т. Д. Все, что имеет значение, - это конечный исполняемый файл.

Теперь вы, кажется, удивлены тем, что одно и то же глобальное имя в программе (= конечный результат объединения всего вместе) всегда ссылается на один и тот же объект. Разве это не сбило бы с толку, если бы это было иначе?

Если file1.cpp и file2.cpp определили переменную A с внешней связью, как компилятор и компоновщик должны знать, хотите ли вы один или два разных объекта? Что еще более важно, как человек читает код, который должен знать, хотел ли первоначальный автор создать один или два объекта?

...