Страницы

Поиск по вопросам

понедельник, 9 декабря 2019 г.

Как надо объявлять и инициализировать глобальную `char *` константу в Си и С++?

#c #cpp #linux


Ситуация следующая. 
Есть функция (в отдельном файле), поведение которой зависит 
от внешней переменной. Эта функция должна вызываться как из программ, в которых 
устанавливается значение этой переменной, так и программ, которые об этой 
переменной ничего не знают (и знать не хотят).
Скажу сразу, что если функция оттранслирована gcc -c, то проблем нет. С ней
нормально работают любые как C, так и C++ программы.
Просто попробовал скомпилировать ее C++ (естественно, будет работать только
с C++ программами) и возникла проблема:
  ext-p.o:(.bss+0x0): multiple definition of `extext'
  /tmp/ccLZRaiS.o:(.data+0x0): first defined here
  collect2: ld returned 1 exit status

Вот файлы:
// ext.h
extern const char *extext;

#ifdef __cplusplus
extern "C" {
#endif

int ptext (const char *t);

#ifdef __cplusplus
}
#endif

Этот заголовочный файл включается во все программы, вызывающие ptext().
// ext-p.c та самая внешняя функция
#include 
#include "ext.h"

const char *extext;

int ptext (const char *p) {
  return puts (p ? : extext ? : "nothing to print");
}

Тут дело в том, что если внешний модуль не использует extext, то ее значение
будет NULL.
Пара программ, вызывающих функцию
// ext-mc.c
#include 
#include "ext.h"

#ifndef __cplusplus
const char *extext = "text test";
#else
const char *extext = "c++ text test";
#endif

int main (int ac, char *av[]) {
  ptext(av[1]);
}

ext-mc.c устанавливает значение etext, а следующая ничего о нем и знать не хочет.
// ext-m.c    
#include 
#include "ext.h"

int main (int ac, char *av[]) {
  ptext(av[1]);
}

Протокол компиляции и запуска
avp@avp-xub11:avparse$ gcc ext-p.c -c
avp@avp-xub11:avparse$ g++ ext-mc.c ext-p.o
avp@avp-xub11:avparse$ ./a.out 
c++ text test
avp@avp-xub11:avparse$ gcc ext-mc.c ext-p.o
avp@avp-xub11:avparse$ ./a.out 
text test
avp@avp-xub11:avparse$ g++ ext-m.c ext-p.o
avp@avp-xub11:avparse$ ./a.out 
nothing to print
avp@avp-xub11:avparse$ gcc ext-m.c ext-p.o
avp@avp-xub11:avparse$ ./a.out 
nothing to print
avp@avp-xub11:avparse$

Видно, что когда файл с ptext() оттранслирован gcc все работает, как задумано.
avp@avp-xub11:avparse$ g++ ext-p.c -c
avp@avp-xub11:avparse$ g++ ext-m.c ext-p.o
avp@avp-xub11:avparse$ ./a.out 
nothing to print
avp@avp-xub11:avparse$ g++ ext-mc.c ext-p.o
ext-p.o:(.bss+0x0): multiple definition of `extext'
/tmp/ccLZRaiS.o:(.data+0x0): first defined here
collect2: ld returned 1 exit status
avp@avp-xub11:avparse$

Собственно, вопрос тот же, что и в заголовке -- Кто-нибудь знает, как запрограммировать
такую штуку, что бы все можно было 
компилировать и gcc и g++?
Update 1
Судя по предложениям в некоторых комментариях наверное в вопросе я недостаточно осветил
проблему.
Есть функция (вообще-то не та, что приведена в примере), которая используется во
многих программах и не вызывается напрямую из main. Она выводит содержимое некой структуры
(один из ее параметров).
Желательно, чтобы дополнительную информацию (константный для программы текст -- автор,
версия и т.п.) не нужно было "тащить" от main(), где она естественным образом определена
до этой функции.
Если функция оттранслирована gcc и лежит в библиотеке, то все ОК. Проблем ни с Си,
ни с С++ программами и способом задания в них этой информации (можно как в примере
в вопросе, а можно просто в начале main написать extext = "bla-bla.." или даже прочесть
из конфига и т.п.).
Если же программист берет файл .c с ней и собирает программу (g++) вместе с другими
своими .cpp файлами и в Makefile явно не прописывает, что ее надо компилировать gcc
-c, то возникает описанная ошибка.
Вопрос, а это вообще реализуется в С++ остается.
Update 2 (смотрю, @mikillskegg появился)
Сейчас мы видим такую картину:
avp@avp-ubu1:avparse$ gcc -c ext-p.c; nm ext-p.o
0000000000000008 C extext
0000000000000000 T ptext
                 U puts
avp@avp-ubu1:avparse$ g++ -c ext-p.c; nm ext-p.o
0000000000000000 B extext
0000000000000000 T ptext
                 U puts
avp@avp-ubu1:avparse$

Может быть решение получится, если ответить на такой вопрос:
Как надо писать ext-p.c, что бы в выводе nm ext-p.o вместо B extext получить C extext?    


Ответы

Ответ 1



Вот это: const char *extext = "text test"; не установка значения, а определение с инициализацией. Получается у вас переменная extext определена и в ext-p.c, и в ext-mc.c. Правильным будет убрать лишние определения из ext-mc.c, и устанавливать extext где-нибудь в main: // ext-mc.c #include #include "ext.h" int main (int ac, char *av[]) { #ifndef __cplusplus extext = "text test"; #else extext = "c++ text test"; #endif int rc = ptext(av[1]); return printf ("ptext: %d\n", rc) < 0; } UPD: В С++ можно инициализировать глобалы результатом произвольного выражения. Поэтому можно сделать как-то так: bool initialized = (extext = "text"); int main() { ... } Можно так: class GlobalInitialization { GlobalInitialization() { extext = "text"; } static GlobalInitialization instance_; }; GlobalInitialization GlobalInitialization::instance_; int main() { ... } Но в C оба эти способа работать не будут.

Ответ 2



Если используются сугубо ГНУтые компиляторы, то почему бы не ослабить (в компоновочном смысле) определение extext в ext-p.c: const char *extext __attribute__((weak)); Тогда и плюсовый компилятор выдаст в nm 0000000000000000 V extext И всё будет успешно работать.

Комментариев нет:

Отправить комментарий