Глобальная реализация переменных
Когда я пишу следующую программу:
файл 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) с размером int 32 бита
EDIT: ЭТО НЕ ВОПРОС ДЛЯ С++, но для 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
Ответы
Ответ 1
gcc
не сообщает об ошибках/предупреждениях. Но g++
делает.
EDIT:
Похоже, C позволяет предварительные определения для переменной.
В вашем случае оба глобальных определения являются ориентировочными, и в этом случае выбирается первый, который видит компоновщик.
Измените файл2 на:
char global = 1; // no more tentative...but explicit.
Теперь, если вы скомпилируете, как и раньше, пробный def в файле1 будет проигнорирован.
Сделайте def def явным:
int global = 1; // in file1
char global = 1; // in file2
теперь ничто не может быть проигнорировано, и мы получаем множественную ошибку def.
Ответ 2
Это связано с тем, что называется "предварительным определением" в C. Сначала, если вы назначаете global
в файлах file1 и file2, вы получите сообщение об ошибке C. Это связано с тем, что global
не определяется заранее в file1 и file2, это действительно определено.
Из стандарта C (ударный удар):
Объявление идентификатора для объекта с областью файлов без инициализатора и без спецификатора класса хранения или с помощью статического элемента класса хранения представляет собой предварительное определение. Если единица перевода содержит одно или несколько предварительных определений для идентификатора, а единица перевода не содержит внешнего определения для этого идентификатора, то поведение в точности совпадает с тем, что единица перевода содержит объявление области файла этого идентификатора, причем составной тип как конца блока перевода, причем инициализатор равен 0.
В вашем случае "единица перевода" (в основном) каждый исходный файл.
О "составных типах":
Идентификатор с внутренней или внешней связью, объявленный в области, в которой предшествующий объявление этого идентификатора видимо, если в предыдущем объявлении указаны внутренние или внешняя связь, тип идентификатора в более позднем объявлении становится составным тип.
Подробнее о предварительных определениях см. этот вопрос и его ответы.
Похоже, что для вашего случая это должно быть поведение undefined, потому что 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
может выдавать пять видов предупреждений. Каждое предупреждение состоит из пары строк: первый описывает только что встреченный символ, а второй описывает предыдущий символ, встречающийся с тем же именем. Один или оба этих символа будут общим символом.
Ответ 3
Какой компилятор вы используете. Что такое платформа? С 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, в С++ переменные в разных единицах перевода имеют точно такое же объявление для работы.
Ответ 4
Компонент позволяет использовать дубликаты внешних данных (хотя я удивлен, что разные типы не создают проблемы). Какой из них вы получаете, зависит от порядка ваших объектных файлов в командной строке ссылки.