LINUX.ORG.RU

GCC 15 ломает код сишникам

 , ,


1

6

Привет, ЛОР!

Из-за того, что GCC по умолчанию переходит на стандарт C23 для компилируемого кода, возникли некоторые интересные нюансы. Так например, начиная с C23 пустой список аргументов у функции теперь будет считаться объявлением с void. То есть, следующие два объявления будут эквивалентными:

void f1() { }
void f2(void) { }

Это крохотное изменение внезапно сломало вагон и маленькую тележку программ под Linux, где используется этот трюк для передачи в функцию по указателю различных аргументов:

typedef int (*fptr)();

int f1(void) { return 1; }
int f2(int a) { return 2 + a; }

int main(void) {
  fptr f;
  int r = 0;
  f = f1;
  r += f();
  f = f2;
  r += f(1);

  return r;
}

Подобный код больше не будет собираться. Сломанными оказались такие программы как: Linux (ядро), bash, iwd, samba, bluez, rustc, gnupg, vde2, sudo, gdb, postgresql, guile, w3m, freeglut, neovim, dnsmasq и куча других.

Бонусом к этому будет добавленный в mbedtls (и не только) баг, вызванный иным порядком инициализации union.

int main() {
  union {
    int dummy;
    struct { int fs[4]; } s;
  } v = { 0 };
  printf("%d\n", v.s.fs[3]);
  return 0;
}

В gcc 14 и ранее код выведет 0, начиная с gcc 15 – мусор.

Плюс ко всему, bool, true и false теперь ключевые слова, что тоже ломает кучу кода. В общем, сишников ждут интересные времена и много работы по исправлению костылей. Возрадуемся же!

За наводку можно поблагодарить забавнишегося @sf:

В общем, сишников ждут интересные времена и много работы по исправлению костылей.

Да не, поставят нужный -std= и будут жить спокойно дальше.

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

это признак плохих скриптов.

А что, есть хорошие?

Сломанными оказались такие программы как: Linux (ядро), bash, iwd, samba, bluez, rustc, gnupg, vde2, sudo, gdb, postgresql, guile, w3m, freeglut, neovim, dnsmasq и куча других.

Сишники иначе вроде как не умеют почему-то :(

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)

А я вот не использую инициализаторы для нестатических переменных - мне они всегда казались чем-то сомнительным, поскольку создают неявное поведение. Всегда лучше явно написать императивный код для этой инициализации. Для статических переменных ситуация другая - они инициализируются на этапе компиляции и никакого неявного кода не генерят (речь про инициализацию константами разумеется, никакую другую я не использую по тем же причинам).

Что касается функций то не понял в чём проблема. Оно раньше не писало варнинг про несовместимый неявный тайпкаст а теперь пишет? Или они варнинг в неотключаемую ошибку переделали?

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

Бывает, когда пишут на новом стандарте, но его не указывают явно. Сборка ломается на старых компиляторах, у которых стандарт по умолчанию ниже. Проблема распространённая и обыденная. Указывать стандарт языка это хороший тон, не указывать - это безалаберность и плохой тон.

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

Я тоже так думал, но оказалось наоборот – почти везде есть. В том же линуксе прибит C11:

KBUILD_CFLAGS += -std=gnu11

Так что не очень понятно, почему ядро накрыло.

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

То есть приблизительно все.

Во всех более-менее крупных и качественных проектах он указан. А вот во всяком дилетантском быдлокоде часто нет.

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

А что, есть хорошие?

У меня например есть две обёртки, одна с -std=c89 вторая с -std=gnu89 (кроме этих флагов ещё другие настройки), а напрямую gcc я не вызываю.

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

Что касается функций то не понял в чём проблема. Оно раньше не писало варнинг про несовместимый неявный тайпкаст а теперь пишет? Или они варнинг в неотключаемую ошибку переделали?

В смысле, не понял? До C23 вот эти два объявления были разными. Теперь они эквивалентны:

void f1() { }
void f2(void) { }
hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от firkax

А ты точно C знаешь?

typedef int (*fptr)();

int f1(void) { return 1; }
int f2(int a) { return 2 + a; }

int main(void) {
  fptr f;
  int r = 0;
  f = f1;
  r += f();
  f = f2;
  r += f(1);

  return r;
}

Вот этот код больше не соберётся. Типа несовместимы.

$ gcc void-arg.c -o void-arg -std=c23                                        
void-arg.c: In function ‘main’:
void-arg.c:11:5: error: assignment to ‘fptr’ {aka ‘int (*)(void)’} from incompatible pointer type ‘int (*)(int)’ [-Wincompatible-pointer-types]
   11 |   f = f2;
      |     ^
void-arg.c:12:8: error: too many arguments to function ‘f’
   12 |   r += f(1);
      |   

В общем, из сишечки удалили возможность делать указатель на функцию с неопределёнными аргументами.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от gaylord

But more frequently it’s use is an accident

Из этого состоит приблизительно всё программирование на Си.

Язык, который выстрелил by accident. В синтаксисе, который выглядит так, будто по клавиатуре повалялся кот by accident.

Программы, которые работают by accident.

Содержащие баги, которые не стреляют в ногу каждый день только by accident.

Я тут в соседней теме рассказываю о проектирвоании своего ЯП в качестве пет-проекта. И вот там в описании проекта у меня есть такие строки, очень в тему:

# О подходе к проектировании ЯП

Чтобы писать практически полезные алгоритмы на императивном ЯП, достаточно нескольких базовых примитивов: ветвления, циклы, функции, и не имеет большого значения, в каком синтаксисе они представлены.

Однако от ЯП как от инструмента не достаточно просто быть пригодным. Для ЯП желательно быть эргономичным и эффективным. Чем большее количество ошибок остаётся скрытым в коде, тем больше времени программист тратит на отладку программы и тем менее качественным получается продукт. Поэтому одним из главных факторов эффективности ЯП как инструмента являются те характеристики, которые помогают программисту выявлять ошибки. Синтаксис и семантика данного языка проектируются так, чтобы по возможности максимизировать такие характеристики.
wandrien ★★
()
Ответ на: комментарий от wandrien

Вот и будет для них уроком, теперь исправят и совместимость с новым стандартом и систему сборки. Для любого мейнтейнера дистрибутивов такие правки - обыденность. Так что никакой сенсации в этом нет, что новая версия компилятора что-то сломала.

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

Так что никакой сенсации в этом нет, что новая версия компилятора что-то сломала.

Согласен.

Но у ТСа работа такая на ЛОРе – провоцировать срачи. В этот раз он повод не очень удачный выбрал, вряд ли в теме получится срач.

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

Вот и будет для них уроком, теперь исправят и совместимость с новым стандартом и систему сборки. Для любого мейнтейнера дистрибутивов такие правки - обыденность. Так что никакой сенсации в этом нет, что новая версия компилятора что-то сломала.

Тут гораздо интереснее не то, что сломалось, а то, что поменялось:

В gcc 14 и ранее код выведет 0, начиная с gcc 15 – мусор.

Уже ншашли в mbedtls. Я подозерваю, что много где ещё не нашли. Я почти уверен, что это вызовет очередной всплеск CVE через пару лет.

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

Не указанный при компиляции стандарт - нужно считать за UB, то есть ошибку программиста.

То есть, генерить полное говно, которое отстрелит жопу даже случайным прохожим? Потому что суть UB в C примерно такая.

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

В этот раз он повод не очень удачный выбрал, вряд ли в теме получится срач.

Да ладно. Я рассчитываю, что как минимум три-четыре пациента прибегут рассказывать, что обратная совместимости в C никогда никем не гарантировалась, и что всё в порядке вещей.

Или что это всё неправильный компилятор, авторы которого – наркоманы, а вот если компилировать весь код в MSVC97, то всё будет огого!

Или же, наконец, что сишечка из стандарта ISO – не настоящая сишечка, комитетчики – враги народа, а для сборки надо использовать только благословлённый kencc.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

А ты точно C знаешь?

Я не пользуюсь объявлениями с пустыми скобками, потому и не помню точно какие ошибки от них получаются. Как я уже выше писал, на мой взгляд стоило эту конструкцию не синонимом к (void) делать а синтаксической ошибкой.

Вот этот код больше не соберётся. Типа несовместимы.

И вопрос касался как раз «assignment from incompatible pointer type», который стал по дефолту ошибкой, а раньше был просто варнингом. Причём он касается не только функций а и чего-нить типа int *a; char b; a=&b; - раньше оно с дефолтными опциями хоть и писало варнинг, но компилировалось, а теперь падает если не заглушить этот варнинг.

По поводу того что «too many arguments» - фатальная ошибка - да, забыл, думал там тоже будет варнинг. Опять же, я такой код не пишу, а компилирую всё с -Werror, так что можно и подзабыть к какой категории (варнинг или ошибка) относится такая проблема.

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

И вопрос касался как раз «assignment from incompatible pointer type», который стал по дефолту ошибкой, а раньше был просто варнингом. Причём он касается не только функций а и чего-нить типа int *a; char b; a=&b; - раньше оно с дефолтными опциями хоть и писало варнинг, но компилировалось, а теперь падает если не заглушить этот варнинг.

А… то есть, не знаешь.

До C23 объявление с пустыми скобками означало, что набор параметров может быть любым. То есть, это не было кастом к несовместимому типу. Одно из различий между C и C++, кстати.

Только что проверил, код выше собирается с -std=c17 -Wall без каких-либо предупреждений.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от wandrien

Значит и ты тоже. Прочти внимательно о чём я писал, там речь не про поведение функций с пустыми скобками, а про то, при каких условиях компилятор падает с ошибкой.

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

Ты кажется не умеешь читать.

М-да…

Оно раньше не писало варнинг про несовместимый неявный тайпкаст а теперь пишет? Или они варнинг в неотключаемую ошибку переделали?

Какой варнинг? Какой несовместимый тайпкаст? В указанном коде для C17 нет несовместимых тайпкастов.

vadim@aquila:/tmp/1$ gcc -Wall -Werror -Wincompatible-pointer-types void-arg.c -o void-arg -std=c17
vadim@aquila:/tmp/1$ 
wandrien ★★
()
Ответ на: комментарий от hateyoufeel

То есть, это не было кастом к несовместимому типу.

Круто, но это никак не отменяет сказанного мной. Если бы ««assignment from incompatible pointer type», как и раньше, был некритичным варнингом, но это приведение к несовместимому типу, хоть и появилось бы в логах компилятора, но к его падению не привело бы. И скорее всего этот варнинг либо вообще так никто и не прочёл бы, либо для красоты туда бы вставили нужный тайпкаст. И никакой новости про «gcc сломал код» не получилось бы.

Но да, я забыл что несоответствие количества аргументов при вызове - давно неотключаемо-фатальная ошибка, так что да, код бы всё равно сломался.

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

Ошибка будет даже если ты заглушишь диагностику. Потому что теперь это ошибка на уровне языка.

vadim@aquila:/tmp/1$ LANG=C gcc -Wno-incompatible-pointer-types void-arg.c -o void-arg -std=c23
void-arg.c: In function 'main':
void-arg.c:12:8: error: too many arguments to function 'f'
   12 |   r += f(1);
      |        ^
Status: 1
vadim@aquila:/tmp/1$ 
wandrien ★★
()
Ответ на: комментарий от hateyoufeel

Или же, наконец, что сишечка из стандарта ISO – не настоящая сишечка, комитетчики – враги народа, а для сборки надо использовать только благословлённый kencc.

Внесите @zx_gamer!

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

Никто не понимает, что ты тут пишешь, но забудем на секунду об указателях:

C ➤ cat args.c                                                                 
#include <stdio.h>

void f() {
  printf("Hello, LOR!\n");
}

int main(void) {
  f();
  f(1);
  f(1,2);
  f(1,2,3);
  return 0;
}
C ➤ gcc args.c -o args -Wall -Wextra                                           
C ➤ ./args                                                                     
Hello, LOR!
Hello, LOR!
Hello, LOR!
Hello, LOR!
C ➤ gcc args.c -o args -Wall -Wextra -std=c23                                  
args.c: In function ‘main’:
args.c:9:3: error: too many arguments to function ‘f’
    9 |   f(1);
      |   ^
args.c:3:6: note: declared here
    3 | void f() {
      |      ^
args.c:10:3: error: too many arguments to function ‘f’
   10 |   f(1,2);
      |   ^
args.c:3:6: note: declared here
    3 | void f() {
      |      ^
args.c:11:3: error: too many arguments to function ‘f’
   11 |   f(1,2,3);
      |   ^
args.c:3:6: note: declared here
    3 | void f() {
      |  

Так тебе стало понятнее, что именно изменилось?

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от firkax

Это уже другая ошибка, я про неё выше написал уже не раз.

Я тебя перестаю понимать:

Но да, я забыл что несоответствие количества аргументов при вызове - давно неотключаемо-фатальная ошибка, так что да, код бы всё равно сломался.

Ты это назвал «давно неотключаемо-фатальной ошибкой».

Давно - это буквально начиная с C23.

Тут всё упирается в то, как каждый понимает слово давно. Для меня это недавно)

wandrien ★★
()