Глобальная переменная реализация - PullRequest
17 голосов
/ 25 февраля 2010

Когда я пишу следующую программу:

файл 1:

#include <stdio.h>    
int global;    
void print_global1() {
        printf("%p\n", &global);
}

файл 2:

#include <stdio.h>
char global;    
void print_global2() {
        printf("%p\n", &global);
}

файл 3:

void print_global1();
void print_global2();
int main()
{
        print_global1();
        print_global2();

        return 0;
}

выход:

$ ./a.out
0x804a01c
0x804a01c

Вот мой вопрос:

  • Почему компоновщик реализует "int global" и "char global" как одну и ту же глобальную переменную:
  • Почему компилятор не жалуется (не самое маленькое предупреждение с -Wall -Wextra -ansi ...)
  • Как управляется размер глобальной переменной (размеры int и char различны)

PS: Второй вопрос связан с архитектурой / компилятором, поэтому давайте возьмем gcc или Visual C ++ (для C) с размером int 32 бит *

РЕДАКТИРОВАТЬ: ЭТО НЕ ВОПРОС ДЛЯ C ++, НО для C!

Я использую gcc версии 4.4.1 и в Ubuntu 9.10. Вот вывод консоли компиляции:

$ ls
global_data1.c  global_data2.c  global_data.c

$ gcc -Wall -Wextra -ansi global_data*.c
$ ./a.out
0x804a01c
0x804a01c
or 
$ gcc -Wall -Wextra -ansi -c global_data*.c
$ gcc -Wall -Wextra -ansi global_data*.o
$ ./a.out
0x804a01c
0x804a01c

Ответы [ 4 ]

12 голосов
/ 25 февраля 2010

gcc не сообщает ни об ошибке / предупреждениях. Но g++ делает.

EDIT:

Похоже, C позволяет предварительные определения для переменной.

В вашем случае оба глобальных определения являются предварительными, и в этом случае выбирается первое, увиденное компоновщиком.

Измените свой файл2 на:

char global = 1; // no more tentative...but explicit.

Теперь, если вы скомпилируете как раньше, предварительное определение в file1 будет проигнорировано.

Сделайте оба def явными:

int global = 1; // in file1

char global = 1; // in file2

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

5 голосов
/ 25 февраля 2010

Это связано с тем, что называется "предварительным определением" в C. Во-первых, если вы назначите global как в file1, так и в file2, вы получите ошибку в C. Это потому, что global предварительно не определено в файлах file1 и file2 это уже определено.

Из стандарта С (упор мой):

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

В вашем случае, «единица перевода» (в основном) каждого исходного файла.

О «составных типах»:

Для идентификатора с внутренней или внешней связью, объявленного в области, в которой предшествующий объявление этого идентификатора является видимым, если в предыдущем объявлении указан внутренний или внешняя связь, тип идентификатора при последующем объявлении становится составным типа.

Подробнее о предварительных определениях см. этот вопрос и ответы на него .

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

GNU ld имеет опцию --warn-common, которая предупреждает вас о нескольких предварительных определениях (общий символ - это имя компоновщика для предварительно определенных переменных):

$ gcc -Wl,--warn-common file*.c
/tmp/ccjuPGcq.o: warning: common of `global' overridden by larger common
/tmp/ccw6nFHi.o: warning: larger common is here

Из руководства :

Если для переменной есть только (один или несколько) общих символов, она помещается в область неинициализированных данных выходного файла. Компоновщик объединяет несколько общих символов для одной и той же переменной в один символ. Если они имеют разные размеры, он выбирает самый большой размер. Компоновщик превращает общий символ в объявление, если есть определение той же переменной.

Опция --warn-common может выдавать пять видов предупреждений. Каждое предупреждение состоит из пары строк: первая описывает только что встреченный символ, а вторая описывает предыдущий символ с тем же именем. Один или оба символа будут общим символом.

1 голос
/ 25 февраля 2010

Компоновщик допускает дублирование внешних данных, подобных этой (хотя я удивлен, что разные типы не вызывают проблем). Какой из них вы получите, зависит от порядка ваших объектных файлов в командной строке вашей ссылки.

1 голос
/ 25 февраля 2010

Какой компилятор вы используете. Какая платформа? С g ++ я получаю

/tmp/cc8Gnf4h.o:(.bss+0x0): multiple definition of `global'
/tmp/ccDQHZn2.o:(.bss+0x0): first defined here
/usr/bin/ld: Warning: size of symbol `global' changed from 4 in a.o to 1 in b.o

AFAIR, в C ++ переменные в разных единицах перевода имеют одно и то же объявление.

...