LINUX.ORG.RU

clang или gcc кто прав в Си?

 , ,


0

1

Исходный код:

файл a.c:

#include "c.h"
int main(){
    struct struct_h test;
    func_test(&test);
    return 0;
}

файл b.c:

#include "c.h"
void func_test(struct struct_h *test){
    test->test=0;
    return;
}

файл c.h:

struct struct_h{
    int test;
} struct_h;
void func_test(struct struct_h *test);

Компилировать: gcc a.c b.c -o a.out или clang a.c b.c -o a.out

Начиная с GCC версии 10 стало:

/bin/ld: /tmp/ccDFtToH.o:(.bss+0x0): повторное определение «struct_h»; /tmp/ccDWcSDH.o:(.bss+0x0): здесь первое определение
collect2: error: ld returned 1 exit status

В clang нет ошибок, во всех прошлых версиях gcc, я проверял начиная с gcc 5.4 до gcc9, тоже нет обшибок.

Очевидный фикс это добавить typedef для структуры в c.h, просто интересно кто прав в этой ситуации.

https://gcc.gnu.org/gcc-10/porting_to.html

C language issues
Default to -fno-common
A common mistake in C is omitting extern when declaring a global variable in a header file. If the header is included by several files it results in multiple definitions of the same variable. In previous GCC versions this error is ignored. GCC 10 defaults to -fno-common, which means a linker error will now be reported. To fix this, use extern in header files when declaring global variables, and ensure each global is defined in exactly one C file. If tentative definitions of particular variables need to be placed in a common block, __attribute__((__common__)) can be used to force that behavior even in code compiled without -fcommon. As a workaround, legacy C code where all tentative definitions should be placed into a common block can be compiled with -fcommon.


      int x;  // tentative definition - avoid in header files

      extern int y;  // correct declaration in a header file

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от svv20624

gcc просто стал более строгим по умолчанию. Все правы, виноватых нет ::) Юзать кланг смысла нема, по скоростям близко, бинарники жирнее копирует фичи gcc и всё такое прочее. Юзай gcc и не парься, а только в особых случаях Android NDK где шланг прибили гвоздями юзай его.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)

Очевидный фикс -

#ifndef C_H_INC
#define C_H_INC
struct struct_h{
    int test;
} struct_h;
void func_test(struct struct_h *test);
#endif

бОльшая часть инклюдов в /usr/include именно так и сделана.

vel ★★★★★
()

Твоя проблема в том, что:

struct struct_h{
    int test;
} struct_h;

определяет глобальную переменную с именем struct_h в дополнение к самой структуре.

Сделай так:

struct struct_h{
    int test;
};

И ошибки не будет.

А твой фикс с typedef:

typedef struct struct_h{
    int test;
} struct_h;

делает struct_h не переменной, а typedef.

rupert ★★★★★
()

facepalm. static напиши. повторное определение не потому что структура struct_h дважды определена, а потому переменная struct_h два раза включена в .c файлы. у тебя ошибка линковки, а не компиляции.

ты так набросить чтоли решил?

anonymous
()
Ответ на: комментарий от beastie

И чего все так #pragma once не любят?

Нестандарт, но не умеющих компиляторов ещё не встречал (хотя GCC с какого-то бодуна объявляли одно время устаревшей). Работает, если не заниматься извращениями, хорошо, а выглядит поаккуратней, чем «оборачивания» в include guards.

SkyMaverick ★★★★★
()
Ответ на: комментарий от svv20624

получается что clang не соблюдает стандартов?

Соблюдает. Тут неопределённое поведение. Оно не обязано быть диагностированным.

anonymous
()
Ответ на: комментарий от LINUX-ORG-RU

gcc просто стал более строгим по умолчанию.

В каком смысле «более строгим»?

anonymous
()
Ответ на: комментарий от moonmadness

Да какбы коню ясно, но это как-то в корне меняет суть вопроса ТС?

Ну теперь включили -fno-common по умолчанию и GCC резервирует глобалы не COMMON, позволяя компоновщику самому решать куда их пихать: в .bss или .data, а напрямую в .bss, что компоновщик уже не соберёт. И это работает, пока не инициализируешь этот глобал в двух юнитах сразу. При неоднократной инициализации также не соберётся - будет multiple definition of struct_h.

Но ТС-у это вряд-ли сильно интересно.

SkyMaverick ★★★★★
()

В этой ситуации ты саморучно, и того не подозревая, создаёшь глобальную переменную struct_h типа struct struct_h. Все, что тебе надо, это заменить } struct_h; на };. И добавить гарды во все хидеры, но это не связанная проблема.

anonymous
()
Ответ на: комментарий от SkyMaverick

Спасибо за пояснение. Вот этот проект весь пронизан подобными ситуациями со структурами и переменными:

$ git clone https://github.com/probonopd/previous -b master --depth=1
$ cd previous/
$ cmake -DCMAKE_BUILD_TYPE=Release .
$ cmake --build .

Начал было всё вычищать, но там много времени надо потратить.

Получается проще сделать так:

$ CGLAGS=-fcommon cmake -DCMAKE_BUILD_TYPE=Release .
$ cmake --build .

Чтобы ошибки линковки исчезли.

EXL ★★★★★
()
Ответ на: комментарий от SkyMaverick

Ну теперь включили -fno-common по умолчанию

Кстати, почему в GCC и в новой версии Clang этот флаг активировали по-умолчанию? Чем руководствовались? Может кто развёрнуто объяснить?

EXL ★★★★★
()
Ответ на: комментарий от EXL

Ну, если оно собиралось и работало, то да - проще переключить флаг обратно.

SkyMaverick ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

У Clang «автоматический constexpr» круче, и ошибки находит лучше.

paramon
()
Ответ на: комментарий от SkyMaverick

И чего все так #pragma once не любят?

Не все. Я про него вообще не знал, пока не показал кусочек кода чуваку, работающему в яндексе. После чего был поднят на смех.

dimgel ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Юзать кланг смысла нема

Ну как же: встроенный language server, более разумные сообщения об ошибках (особенно в темплейтах), гораздо меньшее количество танцев с бубном для кросскомпиляции. Не говоря уже о том, что будет, если вдруг возникнет задача модифицировать сам компилятор или писать программу, в которую нужно встроить парсер C++.

proud_anon ★★★★★
()
Ответ на: комментарий от proud_anon

более разумные сообщения об ошибках (особенно в темплейтах)

Нет, в gcc лучше.

В clang сообщения короче, но ничего непонятно. gcc не убирает контекст, там нужно лишь прочитать ошибку и понятно в чём она.

fsb4000 ★★★★★
()
20 декабря 2021 г.
Ответ на: комментарий от beastie

★★★★★

Советует код с неопределенным поведением.

Классика.

А потом ещё кто-то на форуме говорит, что деды умели программировать…

fsb4000 ★★★★★
()
struct struct_h{
    int test;
} struct_h;
  ^^^^^^^^ вот тут проблема, убери
salozar
()
Ответ на: комментарий от XMs

Тем не менее, поддерживается даже самым большим нелюбителем стандартов (MSVC). Как-минимум, мне не встречалось компиляторов, которые конкретно эту pragma не поддерживают.

И да, справедливости ради, alloca - тоже нестандарт, как и всякий strdup. И ничего, в каждом втором коде торчать не мешает.

SkyMaverick ★★★★★
()

Некропостеры, остановитесь

anonymous
()
Ответ на: комментарий от SkyMaverick

Тем не менее, поддерживается даже самым большим нелюбителем стандартов (MSVC)

Пусть так, но факта это не меняет.


И да, справедливости ради, alloca - тоже нестандарт, как и всякий strdup

strdup() conforms to SVr4, 4.3BSD, POSIX.1-2001. strndup() conforms to POSIX.1-2008

А вот alloca да, нестандарт, к тому же опасный

XMs ★★★★★
()
Ответ на: комментарий от XMs

Стандарт POSIX != стандарт K&R/ANSI/С99/C11. Тем не менее, даже тотже msvc вполне переваривает.

к тому же опасный

Не спорю. Однако сплошь и рядом. Особенно всякие оптимизаторы любят понавтыкать.

SkyMaverick ★★★★★
()
Ответ на: комментарий от fsb4000

Советует код с неопределенным поведением.

Совсем чайник течёт? Окромя хэда никакого кода и в помине там нет.

anonymous
()
Ответ на: комментарий от anonymous

Пятизвёздочный, не бомби. RTFM.

anonymous
()
Ответ на: комментарий от fsb4000

Админы не умеют в программирование (хотя сами о себе они весьма высокого мнения).

anonymous
()
Ответ на: комментарий от anonymous

Совсем чайник течёт? Окромя хэда никакого кода и в помине там нет.

Там два нижних подчёркивания использовалось. __C_H это зарезервированный идентификатор. Его использование есть UB.

fsb4000 ★★★★★
()
Ответ на: комментарий от SkyMaverick

Параноикам этого мало :) Видел с десяток проектов, где используют и прагму, и гарды с обоснуями от «ну авдрук чо» до «это не мы». Зато смеют что-то кукарекать про «нестандартность фрипаскаля», сорцы интерфейсной либы от зоказчика были исключительно на нем, и ихнего ценного мнения заказчик не спрашивал :)

slackwarrior ★★★★★
()
Ответ на: комментарий от Legioner

Что в нем опасного?

https://man7.org/linux/man-pages/man3/alloca.3.html

The alloca() function returns a pointer to the beginning of the
allocated space.  If the allocation causes stack overflow,
program behavior is undefined.

То что нет обработки ошибок, то что функция не возвращает nullptr, а просто начинается дичь если ты запросил больше места чем можно выделить на стеке.

fsb4000 ★★★★★
()

прав тот, кто сначала изучит документацию по компиляторам, прежде чем сравнивать просто результат компиляции/не компиляции кода.

xmikex ★★★★
()
Ответ на: комментарий от Legioner

Не тоже. malloc обязан возвращать NULL (ENOMEM) при ошибке. alloca всегда возвращает указатель и любая нестандартная ситуация = UB (чаще всего срыв стека).

SkyMaverick ★★★★★
()
Ответ на: комментарий от SkyMaverick

Не тоже. malloc обязан возвращать NULL (ENOMEM) при ошибке.

В линуксе не возвращает. Почитай про оверкоммит.

alloca всегда возвращает указатель и любая нестандартная ситуация = UB (чаще всего срыв стека).

Да-да, именно так malloc и работает. Возвращает тебе ненулевой указатель, а при попытке читать/писать память по этому указателю oom killer прибивает тебя.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

Почитай про оверкоммит

Не путай реализацию malloc в libc и стратегию выделения памяти в ОС. libc работает так, как написано в стандарте. А overcommit - это уже алгоритм работы системного планировщика.

SkyMaverick ★★★★★
()
Ответ на: комментарий от SkyMaverick

Я пишу, как всё работает в реальности, а не в стандарте. В реальности alloca и malloc отличаются лишь тем, что объём стека всего 8 мегабайтаов, в куче всё же побольше места будет.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

В реальности

В реальности ты перепутал кто на ком стоит. А во-вторых не отличаешь ситуации:

  • прибито OOM Killer-ом, потому что полезло за пределы того что есть (если бы не лезло и не прибили). Что и есть overcommit. И к malloc как таковому это имеет отношение примерно никакое.

  • мы выделили сколько-то на стеке. Естественно, при любой ошибке, переколбасили стек и вообще сделали непонятно что, потому что alloca - компилятороспецифичная нестандартная функция и любой косяк - UB с заранее не ясными последствиями.

SkyMaverick ★★★★★
()
Ответ на: комментарий от Legioner

Если закончится память в куче, malloc вернёт NULL. То, что линукс использует оптимистичную модель выделения памяти, ничего не значит, это платформоспецифичная особенность. А вот alloca обеспечит переполнение стека, а там хорошо, если приложение сразу будет прибито

XMs ★★★★★
()
Ответ на: комментарий от XMs

Если закончится память в куче, malloc вернёт NULL.

Без ulimit в линуксе маллок никогда не вернёт NULL.

То, что линукс использует оптимистичную модель выделения памяти, ничего не значит, это платформоспецифичная особенность

В масштабе линукса это уже не особенность, это правило.

А вот alloca обеспечит переполнение стека, а там хорошо, если приложение сразу будет прибито

И это тоже платформоспецифичная особенность. Как и alloca в принципе.

И ничего такого страшного в переполнении стека нет. Просто segv прилетит. Можно даже обработать. Там защитная страница стоит.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

В масштабе линукса это уже не особенность, это правило

А в масштабах больших, чем одна платформа — таки особенность, на которую хорошо и полезно закрыть глаза и делать по-нормальному, потому что на других платформах этой особенности нет.


И ничего такого страшного в переполнении стека нет. Просто segv прилетит. Можно даже обработать. Там защитная страница стоит.

На микроконтроллере, например, ничего не прилетит. Просто в какой-то момент критичное оборудование ВНЕЗАПНО перестанет отвечать или перезапустится, если настроен watchdog. Очень надёжно, всем рекомендую

XMs ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.