Писать действительно переносимый C-код, соответствующий стандарту ISO — редкая и непрактичная задача. Почти любой реальный код полагается на нестандартное поведение и расширения компиляторов. Часто не ради новых возможностей, а просто чтобы обойти ошибки и недочёты в разных компиляторах и библиотеках.
Автор статьи, работая над собственным C-компилятором, собрал коллекцию таких проблем. Первое препятствие — системные заголовки libc. В GNU/Linux это glibc. Сама glibc пытается сохранять совместимость с не-GCC компиляторами через sys/cdefs.h, но работает это криво. Например, структура struct epoll_event из sys/epoll.h помечена атрибутом __attribute__((packed)). Но sys/cdefs.h просто игнорирует __attribute__ для всех, кто не GCC, не clang и не tcc. Даже если ты реализовал поддержку атрибута — компилятор до него не доберётся. С limits.h та же беда: glibc использует GCC-специфичный #include_next, и даже clang вынужден под это подстраиваться.
Дальше — SDL. Заголовок SDL_endian.h определяет, как делать байтовый своп. Логика там странная: если ты не GCC и не clang, но определяешь архитектурный макрос вроде __x86_64__, он попытается вставить inline-ассемблер в стиле GCC. Хотя у тебя могли быть нормальные встроенные функции.
В OpenBSD libc используют макрос __only_inline для встраиваемых функций в заголовках (sigemptyset и т.п.). Это попытка подражать старым gnu89-семантикам inline. На не-GCC компиляторах макрос разворачивается в static, из-за чего возникают конфликты линковки. Правда, можно спастись, определив макрос _ANSI_LIBRARY — тогда эти «оптимизированные» версии просто отключаются.
Gnulib — библиотека совместимости, используемая в Guile и nano. Её макросы для extern inline — это просто бездна. Там проверяют всё: версию GCC, наличие __GNUC_STDC_INLINE__, платформу (Apple, DragonFly, FreeBSD), макросы от HP, Sun, PGI и ещё кучу всего. Если не разберёшься — получишь сломанную сборку.
bionic (Android libc) пошла другим путём: она заточена под clang. Там куча clang-specific атрибутов вроде _Nonnull и _Null_unspecified. К счастью, их можно подавить через флаги командной строки. Автор наткнулся на это, юзая Termux на своём Android-телефоне как нативную среду разработки под aarch64.
Итог: жаловаться на разработчиков бессмысленно — никто не будет тестировать код на десятке мелких компиляторов. У автора компилятора есть четыре пути: патчить всё upstream (безнадёжно), набрать популярность и заставить включать проверки под себя (сложно), тащить патчи в своём дистрибутиве (легко), или притворяться GCC. Последнее — самый реалистичный вариант. Clang, например, выдаёт себя за GCC 4.2.1. Проблема в том, что многие проверяют просто #ifdef __GNUC__ без версии, и хватаются за любые свежие расширения. Поэтому clang и не поднимает свою маску версии. Идеал — макросы проверки возможностей вроде __has_builtin или __has_attribute, а не привязка к конкретному компилятору. Пока же в мире *NIX правит квази-дуополия GCC/clang. Автор отдаёт должное разработчикам маленьких независимых C-компиляторов: tcc, cproc, scc, vbcc, nwcc, kefir.