LINUX.ORG.RU

Авторы Си — наркоманы?

 , , ,


1

5

Столкнулся с интересным багом. После того как разобрался, что же именно происходит, меня постигло крайнее изумление! Оказывается, в языке Си тип числовой константы зависит от формата записи.

Дистиллированный пример кода, который это демонстрирует:

#include <stdbool.h>
#include <stdio.h>

#define IS_HEX(x) \
    _Generic((x), \
        unsigned int: true, \
        long: false \
    )

#define X 0x80000001
#define I 2147483649

int main(void) {
    if(X == I)
        puts("X == I");

    if(!IS_HEX(I))
        puts("I is not hexadecimal");

    if(IS_HEX(X))
        puts("X is hexadecimal");

    return 0;
}

Все три сообщения будут выведены на экран.

Зачем это сделано? Кому от этого легче? Какие оптимизации это позволяет проворачивать, кроме оптимизации отстрела ног программистам? Непонятно! В общем, стремлюсь поделиться своим негодованием здесь и предостеречь будущие поколения от наступления на эти грабли.



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

Вряд ли. int скорее всего останется где был, т.к. иначе 32-битное целое уже не назвать. long, long long, long long long итд для следующих битностей. Но вот учитывать что int может быть и 16 и 32 - надо. А так же то, что long может быть и 32 и 64, а long long - и 64 и 128 (в будущем).

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

C(89) как раз довольно четко стандартизирован.

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

Чо?

C создан в 70-х годах, стандартизирован в 89.

Си из 70х и C89 – фактически разные языки.

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

Вряд ли. int скорее всего останется где был, т.к. иначе 32-битное целое уже не назвать.

Напротив. Логично int называть оптимальное для вычислений число. Скорее всего, размером с регистр, но тут от системы команд зависит. А дальше как обычно - комбинациями short / long.

128 (в будущем).

Не факт, что 128-битные машины в обозримом будущем появятся. Какую величину там хранить? Вот 128-битные расширения, скажем, для дробных чисел - возможно.

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

Предлагаешь short short int для 16 бит, short int для 32 и int для 64? Мне кажется от такого очень много всего поплывёт, разрядность short int не менялась ни разу за всю историю Си и к ней куча всего прибито. Да и к 32-битному int-у много где прибито уже, хотя он и бывает другой. Так что остаётся всё-таки добавлять long-и для новых разрядностей.

А 128-битный int уже есть, если что - начиная с gcc 4.6 на 64-битных платформах есть __int128. До 4.6 был __int128_t но он был с багами, так что можно не учитывать. Ну и на 32-битных платформах вполне есть 64-битный long long уже давно.

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

Достаточно частый, потому что может работать быстро(уб-оптимизация) и позволяет безболезненную арифметику а-б, где б>а

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

Предлагаешь short short int для 16 бит, short int для 32 и int для 64

А может просто… Ну, например… Я даже не знаю… взять уже типы из stdint.h с известной размерностью и перестать трахать мозги себе и окружающим?

Нет, «вагон проблем» вылезает из-за того, что кто-то решил доверять графоманским документам в качестве истины. Задача Си - привести более-менее к универсальному виду прогание под разные платформы, при этом по возможности не потеряв индивидуальные преимущества и просто особенности каждой. Подчёркнутое - важно. Полная унификация приведёт к потере особенностей платформ, а это как раз то, чего допускать нельзя.

Расскажи мне, какие индивидуальное преимущество эксплуатируется в следующем коде?

#include <stdbool.h>
#include <stdio.h>

int main(void)
{
  bool b;
  if(b) puts("true");
  if(!b) puts("false";)
  return 0;
}

Угадаешь, какой код генерит Clang для этого?

Примерно вот такой:

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

Зачем добавлять проверку на переполнение?

Там чтение массива. int idx = i * (run_config->domain_n + 2) + j; Для механизма предподкачки массива надо иметь гарантию, что это число растёт пока работает цикл. Если i или j имеют беззнаковый тип и могут переполниться, то механизм использовать нельзя, чтобы избежать аварийного завершения.

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

А эта предподкачка нужна вообще на x86-64? Как бы при линейном (с фиксированным stride) проходе процессор сам врубает prefetch.

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

Понятия не имею. Упустил, что нить обсуждения про эльбрусы.

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

Предлагаешь short short int для 16 бит, short int для 32 и int для 64?

Почему бы и нет. long long int ведь существует.

разрядность short int не менялась ни разу за всю историю Си и к ней куча всего прибито

Так может, пора «отбить» и переписать наконец в соответствии со стандартом? Ну там uint16_t например.

А 128-битный int уже есть

В risc-v тоже предусмотрен 128-битный стандарт. Не софтовая эмуляция, а именно разрядность ядра. Не уверен, что он до конца допиленный, но все же. Но ни одному производителю это не интересно. Вопрос-то именно в этом: так ли много задач, где нужны настолько большие целые.

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

А 128-битный int уже есть, если что - начиная с gcc 4.6 на 64-битных платформах есть __int128. До 4.6 был __int128_t но он был с багами, так что можно не учитывать. Ну и на 32-битных платформах вполне есть 64-битный long long уже давно.

Кстати, он есть и 256-битный и даже 65535-битный.

#include <limits.h>
#include <stdio.h>

typedef unsigned _BitInt(8388608) enormous;

int main() {
  enormous i = -1;
  printf("%d\n", BITINT_MAXWIDTH);
  return 0;
}

Скомпилируй с -std=c23 и удивись.

$ gcc bitint.c -std=c23
$ ./a.out 
65535
$ clang bitint.c -std=c23
$ ./a.out 
8388608

ВОСЬМИМЕГАБАЙТОВОГО ИНТА ХВАТИТ ВСЕМ!

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

О! Вот теперь понятно, откуда и зачем в итоге в C ассемблерные вставки.

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

Да-да. 6 лет люди (аж целая комиссия, подозреваю, что научная) бились над стандартизацией C и получили (прекрасно выверенный) C89. Который используется до сих пор, включая научную сферу, что во многом и определяет его живучесть. А наука и научная сфера, к слову сказать, исторична. Это я к тому, что уже ничего не поправить, история C уже сложилась. И большая часть полезного написана на нем. C89 с нами навсегда, минимум, как учебный язык, как псевдокод и т.д.

Единственное, подозреваю, что у версий C есть преемственность. И думаю, она будет сохранятся. Иначе всё напрасно. Т.е. через 100 лет, если живы будут люди, можно будет скомпилировать код C89.

P.S. Я есть. =) Я тот самый человек, который писал на C89, после того, как мне надоел PHP (на котором я ранее писал прототипы). И писал до недавнего времени. Последнюю программу написал 1.5 года назад. Сейчас усомнился и даже попросил нейронную сеть определить стандарт своего кода - все на С89. Я прям доволен.

P.P.S. Я свои программы проверял на: Win32 и Win64, на Linux и Android (armv7), даже на ek2 (Эльбрус, был когда-то демо-доступ к общедоступному стенду). Некоторые из них, совсем стандартные, работают без изменений.

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

А может просто… Ну, например… Я даже не знаю… взять уже типы из stdint.h с известной размерностью и перестать трахать мозги себе и окружающим?

Я типы с известной размерностью использую, но беру не из stdint.h а из fcl/types.h. Однако речь была не про них, а про возможные изменения разрядности простого int и возможные проблемы (или их отсутствие) совместимости кода, его использующего. Так что твой комментарий не в тему.

Расскажи мне, какие индивидуальное преимущество эксплуатируется в следующем коде?

Никакое, это баг шланга. Речь была опять не про это, а про то что на разных платформах разное поведение и не надо этого стесняться. И UB придумывать в этих местах тоже не надо, как это некие графоманы делают.

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

Так может, пора «отбить» и переписать наконец в соответствии со стандартом? Ну там uint16_t например.

Нет, лучше отобьём руки тем кто хочет портить старый код на ровном месте.

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

Там слишком длинная статья, я её не осилил. Посмотрел упомянутый цикл, напишу его тут чтобы избавить остальных интресующихся от мучительных поисков

for (int i = start_i; i <= stop_i; ++i) {
    for (int j = start_j; j <= stop_j; ++j) {
        int idx = i * (run_config->domain_n + 2) + j;
        double wij  = src[idx];
        double wipj = src[(i + 1) * (run_config->domain_n + 2) + j];
        double wimj = src[(i - 1) * (run_config->domain_n + 2) + j];
        double wijp = src[i       * (run_config->domain_n + 2) + j + 1];
        double wijm = src[i       * (run_config->domain_n + 2) + j - 1];
        double x = (run_config->start_i + i - 1) * run_config->h1;
        double y = (run_config->start_j + j - 1) * run_config->h2;
        double laplacian = (wipj + wimj - 2 * wij) * run_config->sqinv_h1 + (wijp + wijm - 2 * wij) * run_config->sqinv_h2;
        dst[idx] = q(x, y) * wij - laplacian - alpha * F(x, y);
    }
}

И немного пооффтоплю (кажется, это не связано с упомянутыми оптимизациями int-long-unsined int), хотя с другой стороны не оффтоп т.к. тема про Си. Так вот, код на мой взгляд совершенно сомнительный, я бы его писал так:

mul = run_config->domain_n + 2;
for (int i = start_i; i <= stop_i; ++i) {
    pos = src + i*mul + start_j;
    for (int j=start_j; j <= stop_j; ++j,++pos) {
        double wij  = pos[0];
        double wipj = pos[mul];
        double wimj = pos[-mul];
        double wijp = pos[+1];
        double wijm = pos[-1];
        double x = (run_config->start_i + i - 1) * run_config->h1;
        double y = (run_config->start_j + j - 1) * run_config->h2;
        double laplacian = (wipj + wimj - 2 * wij) * run_config->sqinv_h1 + (wijp + wijm - 2 * wij) * run_config->sqinv_h2;
        dst[idx] = q(x, y) * wij - laplacian - alpha * F(x, y);
    }
}

И выглядит намного опрятнее, и, подозреваю, компилятору будет проще заметить что строка массива (индексы по j) непрерывна безо всякого signed-ub-подкостыливания.

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

Да-да. 6 лет люди (аж целая комиссия, подозреваю, что научная) бились над стандартизацией C и получили (прекрасно выверенный) C89.

Научного там ничего не было. Так, сборище обрыганов. Почитай истории Реймонда (который ESR), он наблюдал за этим. Над собственно языком прошлись достаточно быстро, всего за год-два. А потом ещё 4 года срались по переписке о содержимом стандартной библиотеки.

Видел как сейчас Wayland разрабатывают, где на каждый чих собирается срач на три года? Вот сишеньку так же делали. Это просто плод больного аутизма, ничего более.

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

Никакое, это баг шланга.

В gcc код просто печатает false. Это тоже баг? Почему все компиляторы такие забагованные-то? Сишники не могут нормальный компилятор сделать?

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

А что плохого печатать false? Твой bool размещён в виртуальном регистре, рандомно оказывающимся false на старте. Или ты хотел чтобы он генератор случайных чисел туда встроил? Или локальная переменная должна обязательно обращаться к оперативной памяти?

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

Не буду от себя больше писать. https://habr.com/ru/companies/vdsina/articles/532416/ 4 комментарий.

Цитирую: «Инструмент вытащили далеко за области его применимости, и обнаружили — внезапно! — что он там плохо работает. И решили его заменить. Ну, да, писать либу размера и функциональности как OpenSSL на С — так себе начинание. Насчёт ядра Linux, например, уже нельзя сказать так уверенно. А что-то, что работает без ОС, «оживить» с помощью С — самое то. Но ведь это нюансы, о которых мало кто думает. У меня многие знакомые программисты при слове «программировать» не вспоминают embedded даже с подсказкой. Типа «на железках дрова сами заводятся, как мыши в сене».»

Пишите себе на чем хотите и оставьте в покое C. Системщики все равно будут писать на нем.

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

habr

Извини, это помойка и статьи там аналогичные.

У меня многие знакомые программисты при слове «программировать» не вспоминают embedded даже с подсказкой

Потому что перекладывание байтиков из регистра в регистр – это не программирование. Не больше чем перекладывания JSON из сокета в Postgres. Примерно одного уровня занятие, и не случайно, что LLM-ки первым делом теснят как раз этих ребят.

Пишите себе на чем хотите и оставьте в покое C. Системщики все равно будут писать на нем.

Ага. С громкими матами и плюясь по сторонам, как я это делаю.

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

А что плохого печатать false? Твой bool размещён в виртуальном регистре, рандомно оказывающимся false на старте. Или ты хотел чтобы он генератор случайных чисел туда встроил? Или локальная переменная должна обязательно обращаться к оперативной памяти?

Ты не понял. GCC выкидывает проверки и заменяет всю программу на int main(void) { puts("false"); }

Внезапно, единственный из популярной тройки, который этого не делает, это MSVC. Тот честно оставляет все проверки. Наверное, только Microsoft могут сишный компилятор без багов сделать.

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

Нет, лучше отобьём руки тем кто хочет портить старый код на ровном месте.

Тоже неплохо. Глядишь, хоть в новом коде подобного делать не будете. Ну, по крайней мере пока руки не заживут.

Ты не понял. GCC выкидывает проверки и заменяет всю программу на int main(void) { puts("false"); }

А что, он не имеет права этого делать? Компилятор решил, что в неинициализированной переменной лежит ноль. Это ничему не противоречит. В отличие от clang с его нерешительной переменной, которая и не true и не false.

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

Потому что перекладывание байтиков из регистра в регистр – это не программирование. Не больше чем перекладывания JSON из сокета в Postgres. Примерно одного уровня занятие, и не случайно, что LLM-ки первым делом теснят как раз этих ребят.

Интересно, LLM-ки осилят написать корректно хоть что-то, что сегодня (по делу, а не for fun) написано на ассемблере? Я уж не говорю про производительность.

Ушёл проверять.

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

Ты не понял. GCC выкидывает проверки и заменяет всю программу на int main(void) { puts(«false»); }

А что, он не имеет права этого делать? Компилятор решил, что в неинициализированной переменной лежит ноль. Это ничему не противоречит. В отличие от clang с его нерешительной переменной, которая и не true и не false.

Согласно мнению @firkax, это баг.

Вообще, конечно, это UB и здесь нет гарантий на поведение кода вообще никаких, поэтому выкинуть такую программу и заменить на тупо return 0; будет корректным. Как и любое другое поведение.

yorshka
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.