Почему моя инструкция препроцессора #ifndef не предотвращает многократное включение заголовка при компиляции?

Редактировать: Решено. Спасибо @Govind-Pramar и @SomeWittyUsername за предоставленное решение, объявление константы как extern в заголовке и инициализация их в файле C работают.

Я работаю над этим проектом: https://github.com/SuperTotoGo/AES_Cipher

И у меня есть заголовочный файл, содержащий константы (все ошибки связаны с константами, определенными в «aes_const.h»), к которым должны обращаться другие модули проекта, и поэтому он включен во все указанные модули. Чтобы избежать множественных включений, я использовал инструкции препроцессора #ifndef/#define, но при компиляции получаю эту ошибку:

gcc -std=c99 -o aes.out aes_ciph_func.c aes_kexp_func.c aes_math.c main.c
usr/bin/ld: /tmp/cc9HKLA4.o:(.rodata+0x0): multiple definition of `AES_SUB_BOX'; /tmp/ccC4gp1r.o:(.rodata+0x0): first defined here
/usr/bin/ld: /tmp/cc9HKLA4.o:(.rodata+0x100): multiple definition of `INV_AES_SUB_BOX'; /tmp/ccC4gp1r.o:(.rodata+0x100): first defined here
/usr/bin/ld: /tmp/cc9HKLA4.o:(.rodata+0x200): multiple definition of `AES_LOG_TABLE'; /tmp/ccC4gp1r.o:(.rodata+0x200): first defined here
/usr/bin/ld: /tmp/cc9HKLA4.o:(.rodata+0x300): multiple definition of `AES_ALOG_TABLE'; /tmp/ccC4gp1r.o:(.rodata+0x300): first defined here
/usr/bin/ld: /tmp/cc9HKLA4.o:(.rodata+0x400): multiple definition of `AES_MULT_MAT'; /tmp/ccC4gp1r.o:(.rodata+0x400): first defined here
/usr/bin/ld: /tmp/cc9HKLA4.o:(.rodata+0x410): multiple definition of `INV_AES_MULT_MAT'; /tmp/ccC4gp1r.o:(.rodata+0x410): first defined here
/usr/bin/ld: /tmp/cc9HKLA4.o:(.rodata+0x420): multiple definition of `RCON'; /tmp/ccC4gp1r.o:(.rodata+0x420): first defined here
/usr/bin/ld: /tmp/ccGpzVgH.o:(.rodata+0x0): multiple definition of `AES_SUB_BOX'; /tmp/ccC4gp1r.o:(.rodata+0x0): first defined here
/usr/bin/ld: /tmp/ccGpzVgH.o:(.rodata+0x100): multiple definition of `INV_AES_SUB_BOX'; /tmp/ccC4gp1r.o:(.rodata+0x100): first defined here
/usr/bin/ld: /tmp/ccGpzVgH.o:(.rodata+0x200): multiple definition of `AES_LOG_TABLE'; /tmp/ccC4gp1r.o:(.rodata+0x200): first defined here
/usr/bin/ld: /tmp/ccGpzVgH.o:(.rodata+0x300): multiple definition of `AES_ALOG_TABLE'; /tmp/ccC4gp1r.o:(.rodata+0x300): first defined here
/usr/bin/ld: /tmp/ccGpzVgH.o:(.rodata+0x400): multiple definition of `AES_MULT_MAT'; /tmp/ccC4gp1r.o:(.rodata+0x400): first defined here
/usr/bin/ld: /tmp/ccGpzVgH.o:(.rodata+0x410): multiple definition of `INV_AES_MULT_MAT'; /tmp/ccC4gp1r.o:(.rodata+0x410): first defined here
/usr/bin/ld: /tmp/ccGpzVgH.o:(.rodata+0x420): multiple definition of `RCON'; /tmp/ccC4gp1r.o:(.rodata+0x420): first defined here
/usr/bin/ld: /tmp/ccZWo13j.o:(.rodata+0x0): multiple definition of `AES_SUB_BOX'; /tmp/ccC4gp1r.o:(.rodata+0x0): first defined here
/usr/bin/ld: /tmp/ccZWo13j.o:(.rodata+0x100): multiple definition of `INV_AES_SUB_BOX'; /tmp/ccC4gp1r.o:(.rodata+0x100): first defined here
/usr/bin/ld: /tmp/ccZWo13j.o:(.rodata+0x200): multiple definition of `AES_LOG_TABLE'; /tmp/ccC4gp1r.o:(.rodata+0x200): first defined here
/usr/bin/ld: /tmp/ccZWo13j.o:(.rodata+0x300): multiple definition of `AES_ALOG_TABLE'; /tmp/ccC4gp1r.o:(.rodata+0x300): first defined here
/usr/bin/ld: /tmp/ccZWo13j.o:(.rodata+0x400): multiple definition of `AES_MULT_MAT'; /tmp/ccC4gp1r.o:(.rodata+0x400): first defined here
/usr/bin/ld: /tmp/ccZWo13j.o:(.rodata+0x410): multiple definition of `INV_AES_MULT_MAT'; /tmp/ccC4gp1r.o:(.rodata+0x410): first defined here
/usr/bin/ld: /tmp/ccZWo13j.o:(.rodata+0x420): multiple definition of `RCON'; /tmp/ccC4gp1r.o:(.rodata+0x420): first defined here
collect2: error: ld returned 1 exit status

Которые, если я правильно понимаю, вызваны тем, что мой заголовок был включен несколько раз.

aes_const.h выглядит так:

#include <stdint.h>

#ifndef AES_CONST_H
#define AES_CONST_H

//CONSTANTS ARE DECLARED HERE

#endif

мои модули выглядят в основном так:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include "aes_kexp_func.h"
#include "aes_const.h"

//FUNCTIONS ARE IMPLEMENTED HERE

заголовок, содержащий прототип функций модулей:

#ifndef MODULE_NAME_H
#define MODULE_NAME_H

//FUNCTIONS PROTOTYPES ARE HERE

#endif

и мой основной файл:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include "aes_const.h"
#include "aes_ctypes.h"

#include "aes_math.h"
#include "aes_ciph_func.h"
#include "aes_kexp_func.h"

main(){/*code here*/}

Разве мой #ifndef не должен предотвратить это? Что мне не хватает?


person Balocre    schedule 27.09.2018    source источник
comment
ну да ладно, будет хрень...   -  person Balocre    schedule 28.09.2018


Ответы (3)


Защитные средства включения (формы #ifndef _HEADER_NAME_ и #pragma once) предотвращают повторное включение одного и того же заголовка в одну и ту же единицу трансляции, а не включение в разные TU.

Что вы можете сделать, это:

  1. Замените определения констант в aes_const.h объявлениями extern, например так:

    extern const uint8_t AES_SUB_BOX[16][16];
    
  2. В любом из ваших исходных файлов полностью определите глобальные переменные:

    const uint8_t AES_SUB_BOX[16][16] = { /* your initialization here */ };
    
person Govind Parmar    schedule 27.09.2018
comment
Это тоже сработало, но объявление констант статическими помогло и сэкономило мне файл. - person Balocre; 28.09.2018
comment
@SuperTotoGo: объявление констант static в заголовке означает, что каждый отдельный файл, включающий заголовок, имеет свой собственный набор констант. Это, безусловно, работает, но занимает немного места, которое можно было бы сэкономить, выполнив работу «правильно» — определив массивы один раз в одном исходном файле и используя заголовок, который объявляет массивы во всех соответствующих исходных файлах. - person Jonathan Leffler; 28.09.2018

#ifndef — это инструкция времени компиляции. У вас возникли проблемы во время связывания. Каждый из ваших исходных файлов компилируется отдельно, но после этого они соединяются вместе. Поэтому, если у вас есть константы, определенные в заголовочном файле, который включается несколькими исходными файлами, вы получите коллизии во время компоновки. Эмпирическое правило для заголовочных файлов: объявляйте данные, но не определяйте их. Если у вас есть константы, к которым должны обращаться несколько модулей, определите их в одном модуле и объявите как extern в общем заголовочном файле.

person SomeWittyUsername    schedule 27.09.2018
comment
Извините, я устал, я забыл нажать репо, константы объявлены как внешние... - person Balocre; 28.09.2018
comment
@SuperTotoGo Нет, вы упускаете суть. Простое добавление extern не решает проблему. У вас все еще есть константы, определенные в заголовке (вы только что явно объявили их как extern). Вам нужно разделить определение и объявление. Переместите определение в файл .c и оставьте объявление extern в файле .h (вы можете найти ссылку в ответе GovindPramar) - person SomeWittyUsername; 28.09.2018
comment
Ох, хорошо. Но объявление их статическими тоже помогло, даже если, по-видимому, это немного причудливо по сравнению с тем, что вы предлагаете. - person Balocre; 28.09.2018
comment
@SuperTotoGo В случае констант вы можете использовать статические и иметь ту же функциональность. Однако вы получите несколько копий одних и тех же статических данных, что существенно увеличит размер вашего двоичного файла. Более того, это концептуально неверно, ИМХО. Ваша логика предполагает, что данные являются общими, а не реплицированными. И если у вас непостоянные данные, у вас будет неправильная функциональность (поскольку каждый модуль будет работать со своей собственной копией данных). - person SomeWittyUsername; 28.09.2018
comment
Я пойду за вашим решением. Мне придется изучить, как работают компиляция и компоновка, потому что я все еще упускаю некоторые моменты. - person Balocre; 28.09.2018
comment
@SuperTotoGo просто помните - в файлах .h нет кода или данных. В файлах .h вы только сообщаете компилятору, что что-то с именем xxxxx действительно определено где-то в файлах .c. - person 0___________; 28.09.2018
comment
@P__J__ подойдет - person Balocre; 28.09.2018

Вы совершаете простую ошибку. Вы объявляете реальные данные и возможный код в файлах .h. Вы должны делать это только в файле C. В файлах .h все переменные должны быть объявлены как внешние, которые выдают только символ, но не сам объект.

person 0___________    schedule 27.09.2018