Пример единицы перевода в зависимости от области действия файла

#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);
}