#c
#c
Вопрос:
В качестве примера рассмотрим следующую программу:
// myprogram.c
#include<stdio.h>
int a, b;
int main(void)
{
printf("A: %dn", a);
printf("B: %dn", b);
}
// friendsprogram.c
int a=1;
static int b=2;
$ gcc myprogram.c friendsprogram.c -o out; ./out
Ответ: 1
B: 0
Как будут классифицироваться единицы перевода в приведенном выше? И чем это будет отличаться от содержимого файлов «myprogram.c» и «friendsprogram.c»? Зависит ли единица перевода когда-либо от команды, которая выдается компилятору? Например, если я изменю команду на просто:
$ gcc myprogram.c -o out; ./out
Мой вывод становится:
A: 0
B: 0
Комментарии:
1. Попробуйте
‐fno-common
.2. @JonathonReinhart что вы хотите немного уточнить, показывая разницу между ними?
Ответ №1:
Единица перевода — это исходный файл вместе со всеми включенными в него заголовками, который компилируется как единое целое.
В этом примере myprogram.c вместе с заголовком stdio.h является одной единицей перевода. Файл friendsprogram.c является еще одной единицей перевода.
Обратите внимание, что это не меняется при компиляции следующим образом:
gcc myprogram.c friendsprogram.c -o out
Потому что эта командная строка объединяет компиляцию и компоновку в один шаг. Создается временный объектный файл для myprogram.c, а другой — для friendsprogram.c, затем эти объектные файлы связываются для создания файла «out».
Комментарии:
1. есть ли способ выполнить только компиляцию, а не связывание с gcc? И если да, будет ли у него один выходной файл для каждого входного файла или как это будет работать?
2. @samuelbrody1249 Примером отдельной компиляции и компоновки может быть
gcc -c myprogram.c ; gcc -c friendsprogram.c ; gcc myprogram.o friendsprogram.o -o out
3. понял, спасибо. Итак, в основном «единица перевода» — это все, что вошло бы в объектный файл, если бы этот исходный файл C был скомпилирован независимо, правильно?
4. @samuelbrody1249 Правильно
5. Обратите внимание, что компилятор понятия не имеет, является ли что-то заголовочным файлом или нет — это все просто файлы. Это означает, что «myprogram.c» может сделать
#include "friendsprogram.c"
(или «friendsprogram.c» может сделать#include "myprogram.c"
), чтобы поместить все в одну и ту же единицу перевода. Этим можно злоупотреблять для лучшей оптимизации (например, создавать большинство функцийstatic
и в итоге получать что-то вроде оптимизации всей программы без использования оптимизации времени соединения).
Ответ №2:
То, что происходит, является побочным эффектом старого снисходительного поведения компилятора / компоновщика и так называемого «общего» раздела.
int a;
В спецификации C указано, что эта переменная глобальной области инициализируется нулем. Можно было бы ожидать, что это войдет в раздел .bss
(данные с нулевой инициализацией) исполняемого файла.
Но в GCC <10 переменная помещается в раздел «общие» при компиляции этого файла (единицы перевода).
int a=1;
Теперь вы предоставили инициализацию, и эта переменная перейдет в .data
раздел.
Но когда компоновщик связывает эти два объектных файла вместе, вместо того, чтобы выдавать ошибку «несколько определений» (для одного и того же имени), он сделает что-то противоречивое и объединит их в одну переменную из-за общей семантики раздела.
При передаче -fno-common
или использовании GCC>= 10 общий раздел не используется, и компоновщик выдаст ошибку и откажется связать вашу программу.
Итак, что вам следует делать? Просто: предоставьте только одно определение для любого имени.
Если вы действительно хотите использовать глобальные переменные (вообще нежелательно), и вы хотите поместить их в отдельную единицу перевода (странно), используйте extern
в других ваших файлах:
data.h
// Declaration: Tells everyone that 'a' exists somewhere
extern int a;
data.c
#include "data.h"
// Definition: defines the variable and its initial value
int a = 42;
main.c
#include <stdio.h>
#include "data.h"
int main(void)
{
printf("a = %dn", a);
}