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)
Ответ на: комментарий от yorshka

потому что современные процессоры

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

можно сделать аппаратную реализацию

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

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

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

Я ничего не пытаюсь. Я лишь указал, ты – первый встреченный мною персонаж, утверждающий, что лямбда-исчисление – низкоуровневый ЯП.

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

Тогда выходит, что этот уровень не является свойством языка самого по себе. Что звучит достаточно странно всё же.

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

Плюс, оно на самом деле вообще не нужно в современных реалиях.

Это fake news. Они то ли по незнанию, то ли специально, взяли бенчмарки из phoronix-test-suite — но там почти во всём софте, который тестируется, узкие места переписаны на ассемблере. Эти дебилы ещё бы libgmp побенчили.

Особенно смешно в этом списке смотрится LuaJIT. Там даже с -joff код исполняется в VM, написанной на ассемблере для каждой архитектуры отдельно. Я вот собрал LuaJIT с -O0, запустил его vs системный на их бенче — разница если и есть, то в пользу -O0:

[~/repo]-[%] for LJ in luajit ./LuaJIT-OPT0/src/luajit; do for arg in '' -joff; do echo "$LJ ${arg:-default}:"; $LJ $arg scimark.lua -large | tail -n1; done; done
luajit default:
SciMark  1512.15  [large problem sizes]
luajit -joff:
SciMark   156.05  [large problem sizes]
./LuaJIT-OPT0/src/luajit default:
SciMark  1538.98  [large problem sizes]
./LuaJIT-OPT0/src/luajit -joff:
SciMark   171.22  [large problem sizes]
[~/repo]-[%]
shdown ★★
()
Последнее исправление: shdown (всего исправлений: 1)
Ответ на: комментарий от shdown

Это fake news. Они то ли по незнанию, то ли специально, взяли бенчмарки из phoronix-test-suite — но там почти во всём софте, который тестируется, узкие места переписаны на ассемблере. Эти дебилы ещё бы libgmp побенчили.

Тем более, какой смысл в этом UB, если всё равно приходится узкие места на асме переписывать? Абсолютно бесполезные жертвы.

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

Так нужно прикладной софт, написанный на Си или C++, тестировать, а не низкоуровневые библиотеки, JIT-компиляторы и подобное. Chromium, Firefox (ладно, я не знаю, сколько там раста теперь). Интерпретаторы, написанные на Си без JIT (PUC Lua, CPython, MRI Ruby etc). Да хоть даже std::sort.

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

ты – первый встреченный мною персонаж, утверждающий, что лямбда-исчисление – низкоуровневый ЯП.

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

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

Тут точно так же как и с функциональными/императивными/объектно-ориентированными языками. На чём угодно можно писать как угодно (в теории). Можно макросами сделать оо-ассемблер, на С передавать указатели на функции в функции и писать императивные последовательности действий в хаскелле. Только это будет глупо и неудобно. Каждый язык диктует стиль как его использовать с минимальными количеством мучений. Поэтому одни языки мы называем функциональными, другие императивными и т.п.

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

strict-aliasing включать даже не думай, тут нечего обсуждать

В этом нашем расте нет стрикт-алиасингого УБ как такового, нечего отключать

фантазии комитетчиков в чистом виде, не обращай внимания.

Что ты несёшь, это у тебя безграмотные фантазии, и компилятору плевать на них, он постоянно использует это УБ в соответствии с документацией. Это вообще одно из основных оптимизационных УБ(и не только в си), и выключить нельзя если хоть какая-то оптимизация нужна, начиная с O1: https://godbolt.org/z/ooWPxf34Y. Это если хочешь таким черезжопным способом добраться до v через каст *i к *Y, кстати, зачем? Можно ещё конкретно в этом примере поставить O0, но гарантии не будет что завтра не выстрелит. И нахрен так жить если весь смысл существования сишки в том что под неё до сих пор компиляторы задрачивают, несмотря на её непригодность для автоматической оптимизации.

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

Эм, и что ты мне хотел этим показать? Всё работает как и ожидалось, никаких некорректных оптимизаций там нет.

Только речь была не про это. Вот правильный пример: https://godbolt.org/z/r6Pz5xzoh И тоже скомпилился в правильный код.

Это если хочешь таким черезжопным способом добраться до v

Обсуждая твой пример: до v ты так не доберёшься. Она размещена в регистре и адреса в памяти у неё нет. А даже если бы был - совсем не факт, что она бы располагалась после i. Размещение локальных переменных в стеке не зафиксировано. Более того, одна и та же переменная может в ходе работы функции переезжать из стека в регистр и назад, и это тоже норм.

А вот этот пример ты мне должен был прислать чтобы убедить в наличии некорректных оптимизаций:

https://godbolt.org/z/G9br34aKK

тут компилятор не должен был считать что v==55 в конце функции, а должен был прочитать её из памяти.

Однако полезности кода ((struct X*)addr)->field это не отменяет.

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

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

Ты вообще кто е ‘штооо’ ниче не процитировал, это же не меняет фактов

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

во временна оные для этого lint был почти сразу - но ща все любят всё в одном (gcc али ещё какой шланго llvm)

а причина одно - безблагодатность а если более точнее зачем быть если достаточно казаться али по не местному fakit till makeit

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

вот на этом молчаливом согласии(жаль тока что плюсы из настоящего си исключили(ну почти - впрочем как и objectC))

зря всёж Вирт не ПаскалеИменовал все свои языки - учитывая общественные инерции ща бы писали бы на Oberone-26 по сути и на Pascal по названию

всёж целая лаба 13-XX с Евангелистом Керниганом покитовослоновее одного цюрихского прохфесора

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

Сишечка реально чем сильна (ну была гдето до «ошибки2к» которая жаба) - была отличным входным фильтром отсекавшая «не могущих в указатели»( ибо чутка переусложняла их использование видимой лёгкостью повсемостного использования с кривым синтаксисом) - а так кривой алгол потомок с текстовыми вставками (даже модульность до сих пор оселок в коммитетах)

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

А вы как считаете, стоит оставить Си в покое вместе с UB или с этим непременно «нужно» что-то делать?

Почти любой компилятор позволяет использовать «Си без оптимизации UB» с оптимизацией -O0.

То, что можно доопределить все UB до какого-то единого соглашения, это понятно.

Если из Си и Си++ убрать UB, они становятся вариантом Java, а Java уже есть. Си и Си++ нужны только тогда, когда требования скорости работы не позволяют использовать Java.

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

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

Был Symbolics 3640.

Аппаратная сборка мусора и команды процессора соответствовали примитивам лиспа.

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

https://en.wikipedia.org/wiki/Combinatory_logic#In_computing

о сколько нам открытий чуждных

забавно как перетолковывают «учителя-методисты» фундаментальные статью

в частности статья(посмертная) фонНеймана https://en.wikipedia.org/wiki/The_Computer_and_the_Brain - она была очень в контексте автоматизации стада вичислительниц чудом электротехники

ну и знаменитая Дейкстровская «ходить на - это вредно»

вторая фундаментальней чем оней принята(было - ща её так из загашников иногда достают как правило не понимая сути и подчёркивая какие были дикие времена)калякать

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

уместней тут

https://en.wikipedia.org/wiki/Combinatory_logic#In_computing

ваще полезно с Жакарда и не состоявшейся?! мельницы и склада Булю с причастной глубокой Адой начать - ну там история телеграфа - сортировочной машины Холерита(почему и когда одни сортировки лучше других :) ) - архитектура «фонНеймана» не появись вовремя транзисторы и ис(бис(сбис)) была бы другой

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

«Hello, world!»

в Сяшке это синтаксический сахар посредством препроцессора(отдельного либо как фазы копулятора в асм/машкуд)облегчающий кодеру занос констант и их последовательностей

ибо вся строковая машинерия вынесена в набор (хоть и стандартизированных через 20 лет после появления переносимого ассемблера) внешних функций

в том и Сила Сишки что модель целевой машины С(PL) очень уж когерентна(особенно по началу) была вспышке микропроцесорных isa

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

препроцессор часть стандарта

не случайно cpp шутка что это плюсы это же бэкроним - там же был вполне стандартный транслятор в Си через препроцессор

кста (мой велосипед но увереность укрепляется) что синтаксис for(init;test;postaction) это буквально обусловленно препроцессорным подходом Ритчи

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

термин «низкоуровневый» язык достаточно бессмысленным получается

Если нет осмысленной методики оценки уровня языка.

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

Обсуждая твой пример: до v ты так не доберёшься. Она размещена в регистре и адреса в памяти у неё нет. А даже если бы был - совсем не факт, что она бы располагалась после i. Размещение локальных переменных в стеке не зафиксировано. Более того, одна и та же переменная может в ходе работы функции переезжать из стека в регистр и назад, и это тоже норм.

Так я про это УБ и говорю, которое позволяет такие оптимизации, и которое ты обозвал «фантазиями комитетчиков». Ты там определись. Явный каст из числа в указатель - это да, не UB (пока ещё, гы), но если размеры и/или layout кастуемых не совпадают, то влетаешь в это самое UB.

А вот этот пример ты мне должен был прислать чтобы убедить в наличии некорректных оптимизаций: https://godbolt.org/z/G9br34aKK тут компилятор не должен был считать что v==55 в конце функции, а должен был прочитать её из памяти.

Чего? Функция отработала и завершилась и таки прочитала значение 33 на момент вызова. Почему v=55 должно влиять на неё. Какие бы то ни было оптимизации тут вообще ни при чём. Или это про что?

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

Ты пишешь какую-то ерунду, что в первом ответе что во втором.

Хранение переменных в регистрах это никаким боком не UB, и даже не совсем оптимизация, это просто способ замапить переменную на низкоуровневое хранилище.

А во второй функции содержимое v должно было быть прочитано из оперативной памяти во время return, чего сделано не было, вместо этого там захардкожен 55. Число 33 во втором примере вообще ни при чём, речь не про него.

Вот так должно было быть:

        mov     [rsp+8], 55
        movsx   rax, DWORD PTR [rsp+12]
        mov     DWORD PTR [rax+4], 42
        mov     eax, [rsp+8]
потому как mov ..., 42 тут уже может перезаписать v - вызовом f(&v,&i) мы обязали компилятор, с момента этого вызова, хранить v строго в оперативной памяти и никуда не двигать.

Кстати, если там ->a заменить на ->b то код становится правильным. Видимо компилятор ломается на попытке вычисления адреса поля 'a' и не учитывает нормальным образом эту запись.

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

LLМ использую как справочник

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

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

Сишечка реально чем сильна (ну была гдето до «ошибки2к» которая жаба) - была отличным входным фильтром отсекавшая «не могущих в указатели»( ибо чутка переусложняла их использование видимой лёгкостью повсемостного использования с кривым синтаксисом)

Половина сишечников тебе на чистых щщах будут утверждать, что указатель – это просто число, адрес в памяти. Хотя это нихрена не так.

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

Так нужно прикладной софт, написанный на Си или C++, тестировать, а не низкоуровневые библиотеки,

Думаешь, будет большая разница? Нет, её не будет, потому что и тут не было.

не низкоуровневые библиотеки, JIT-компиляторы и подобное

Chromium, Firefox (ладно, я не знаю, сколько там раста теперь).

Пишешь, что не надо тестировать JIT-компиляторы, и тут же приводишь два JIT-компилятора как пример для тестирования.

Плюс, тестировать таких монстров весьма сложно, слишком много источников недетерминизма: сеть, взаимодействие тредов, планировщик, etc. Поэтому люди и пишут бенчмарки.

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

Хранение переменных в регистрах это никаким боком не UB

А я подобного и не утверждаю. UB, как раз, позволяет хранить переменную в регистре и не таскать лишний раз дорого и бессмысленно значение в память и обратно(как ты ниже демонстрируешь), а так же и другие оптимизации. Представь что к моменту возврата из функции v успеет вылезти из кэша проца, и во что твоё " mov eax, [rsp+8]" обойдётся. Так что твоя воображаемая «настоящая» сишечка будет очень тормозным языком на современных архитектурах

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

Нет, переменную в регистре можно хранить если нет причин хранить её строго в памяти. Причиной хранить строго в памяти может быть вычисление её адреса и отправка его куда-то наружу, как тут и делается (в функцию f). После этого хранить её в регистре можно только там, где нет шансов её чем-то перезаписать.

Представь что к моменту возврата из функции v успеет вылезти из кэша проца, и во что твоё " mov eax, [rsp+8]" обойдётся.

Всё на усмотрение программиста. Если не вычислять адрес &v и тем более не сообщать его внешней функции - можно было бы хранить в регистре, а так нельзя, в компиляторе соответственно баг.

firkax ★★★★★
()

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

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

Слава Гребенщикова с городом золотым, что питонить прощее

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

А что это тогда такое? Если их можно сравнивать и выполнять арифметические операции? Число и есть, и нехрен нам тут наводить тени на плетень.

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

Да ты твердолобый какой-то.

Думаешь, будет большая разница?

Понятия не имею. Я про методологию говорю.

Нет, её не будет, потому что и тут не было.

«Когда мы вылили стакан воды в одну бочку с говном, и стакан пива в другую бочку с говном, пахло одинаково. Следовательно, разницы между водой и пивом по запаху нет, потому что и тут её не было.»

Пишешь, что не надо тестировать JIT-компиляторы, и тут же приводишь два JIT-компилятора как пример для тестирования.

Я показал, что если скомпилировать LuaJIT с -O2 и с -O0, то разницы в производительности не будет. Потому что там VM написана на ассемблере, и код в рантайме он генерирует на ассемблере. Сишка там не узкое место в производительности.

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

Вот если обычную Lua (там нет ни одной строчки на ассемблере, JIT тоже нет — только сишка) скомпилировать с -O2 vs -O0, там будет разница в 4 раза по производительности:

[~/repo]-[%] lua5.5 scimark.lua -large
Lua SciMark 2010-12-10 based on SciMark 2.0a. Copyright (C) 2006-2010 Mike Pall.

FFT        60.89  [1048576]
SOR       219.16  [1000]
MC         39.36  
SPARSE    152.12  [100000, 1000000]
LU        166.88  [1000]

SciMark   127.68  [large problem sizes]
[~/repo]-[%] ./lua55/lua-5.5.0/src/lua scimark.lua -large # это я собрал с -O0
Lua SciMark 2010-12-10 based on SciMark 2.0a. Copyright (C) 2006-2010 Mike Pall.

FFT        26.90  [1048576]
SOR        75.93  [1000]
MC         16.65  
SPARSE     52.40  [100000, 1000000]
LU         62.44  [1000]

SciMark    46.86  [large problem sizes]
[~/repo]-[%] 

А для LuaJIT разницы не видно, потому что там ассемблер. И они ещё почему-то даже параметр -joff не передают ему.

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

Они даже в яве. «Ссылки в Java — указатели на объекты. Другими словами, ссылка — это переменная, содержащая адрес ячейки памяти, в которой хранится объект. Кроме того, ссылка может быть инициализирована как null — нулевая ссылка, не указывающая ни на какой объект в памяти (именно это значение является значением по умолчанию)».

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

Плюс, тестировать таких монстров весьма сложно, слишком много источников недетерминизма: сеть, взаимодействие тредов, планировщик, etc. Поэтому люди и пишут бенчмарки.

«Взаимодействие тредов» есть и во многих тестируемых программах, например, в pbzip2.

Планировщик какой-то у него… Загружаешься с isolcpus=список_ядер_процессора, отключаешь аппаратные прерывания на этих ядрах, закрепляешь нужные потоки каждый на своём ядре CPU. Никакой планировщик не вмешивается, отклонения во «взаимодействии тредов» минимальные.

сеть

Зачем тебе сеть для бенчмарка браузера? Если там будет иметь значения даже пинг локалхоста, этот бенчмарк можно выбросить.

Поэтому люди и пишут бенчмарки.

Только вот авторы статьи выбрали абсолютно бесполезный для подтверждения или опровержения своего тезиса набор бенчмарков.

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

При нормальных условиях, аммиак — это бесцветный газ. Вылить его будет затруднительно. Одна школота недоученная кругом.

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

А что это тогда такое?

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

Если их можно сравнивать и выполнять арифметические операции?

Нельзя. Сишка запрещает арифметические операции над произвольными указателями, а ещё над NULL. Да-да, в Сишечке выражение NULL + 0 – это UB.

Число и есть, и нехрен нам тут наводить тени на плетень.

А потом ты, как @firkax выше, удивляешься, почему это компилятор твой код превращает в нерабочее говно. Это всё не я придумал, если что, это к авторам стандарта и компиляторов вопросы.

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

Это адрес памяти. Число 64 бита. Операции на ним любые. Не путай операции над указателем и его разыменование.

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

Понятия не имею. Я про методологию говорю.

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

P.S. Из-за любви макскома к индусам я теперь не могу писать чаще чем раз в 5 минут :(((

Следующее сообщение может быть записано не менее чем через 300 секунд после предыдущего

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

В современных оптимизирующих компиляторах тот тип, что обычно обзывается raw pointer, числом является в последнюю очередь, и число в них - это так, малозначимый артефакт, возникающий на поздних этапах компиляции

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

В том и место куда не светит солнце, в Сяшке что типизация указателей в ней изготовлена несколькими поколениями умельцев своего дела, и дело иногда оказалось разное

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

Ахахахахах ещё один сишник не знает Си. Ну сходи в багзиллу GCC, расскажи им, что это просто числа. А то они не знают.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61502

Кстати, пример оттуда до сих пор работает в GCC 16.

$ gcc --version
gcc (GCC) 16.1.0
Copyright (C) 2026 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cat pointers-equality.c         
#include <stdio.h> 
#include <stdbool.h>

int  y = 2, x=1; 
int main()
{
  int *p;
  p = &x +1 ;  
  printf("&x=%p  &y=%p  p=%p\n",(void*)&x, (void*)&y, (void*)p); 
  bool b1 = (p==&y);   
  printf("(p==&y) = %s\n", b1?"true":"false");
  return 0;
}
$ gcc pointers-equality.c -o p -O2
$ ./p                             
&x=0x5eef233e2018  &y=0x5eef233e201c  p=0x5eef233e201c
(p==&y) = false
yorshka
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.