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

ты что ещё не почуствовал?

Бородатый, или даже седой человек, так же соображает, как и ещё не бреющийся, все люди придурки.

Откуда у тебя вера в свитер и бороду?

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

Давай расскажи всем, что делает new() в современных оптимизирующих компиляторах?

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

Тебе же написали убогому, что ссылка это адрес памяти где лежит объект. Явка написана на С, а это внезапно и есть указатель и нехрен тут сопли размазывать, что в С/С++ это не число, не адрес, а нечто такое, что тупым понять не дано.

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

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

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

Ты лишь один странный проект притащил, что ещё хуже

Ну можно посмотреть, какой % из этих проектов использует код на ассемблере. Я думаю, очень большой % получится.

А то, что в списке есть LuaJIT, показывает, что они вообще не одупляют, что происходит в бенчмарках. Поэтому их тезис невалиден. А ты притащил это сюда.

Делай лучше и публикуйся.

Зачем? Мне и так понятно, что многие случаи UB очень сильно помогают оптимизации (пример). А полностью избавиться от UB невозможно хотя бы потому, что нельзя описать, что происходит при out-of-bounds write. UB и в Rust есть. Правда, в Rust и сами не понимают, что является UB. Ну то есть там есть список того, что точно является UB, но «этот список неполный и подлежит расширению в следующих версиях компилятора».

MIRI нашёл очень много Rust-UB в стандартной библиотеке Rust: https://github.com/rust-lang/miri/#bugs-found-by-miri

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

p = &x +1 и на что указывает р?

Какая разница, если это просто число? Кстати, GCC вообще выкидывает сравнение и оставляет просто «false».

https://godbolt.org/z/fbTvd8Ths

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

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

Например, раньше, в дос, были сегменты. И два вида указателей. Внутри одного сегмента памяти — ближние, и абсолютные — дальние. Если два ближних указателя численно равны, они могут относиться к разным сегментам памяти. Поэтому сравнивать указатели бессмысленно.

Кроме того, есть платформы, где указатель не одно число, а целая структура.

Лучше сразу запомнить, что указатель — не число. Даже если иногда квакает как число. И не вводить себя во грех.

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

Внутри одного сегмента памяти — ближние, и абсолютные — дальние

Что это еще за хрень? Процесс работет в виртуальном адресном пространстве или где?

Поэтому сравнивать указатели бессмысленно.

Да ты что:

§ 5.10 of the C++11 standard

Pointers of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.2).

Знаешь, что делает new()? Давай расскажи всем, а мы посмеемся.

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

§ 20.8.5/8: «For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not.»

So, you can globally order any odd void* as long as you use std::less<> and friends, not bare operator<.

Учи матчасть, Маня.

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

Поэтому сравнивать указатели бессмысленно.

Сравнивать численные значения указателей бессмысленно. А сами указатели вполне осмысленно.

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

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

По стандарту там может лежать что угодно, хоть адрес, поксоренный с числом 1337. Никто не запрещает определить такой ABI.

На практике — см. CHERI ISA, Fil-C.

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

А что может быть в указателе кроме численного значения (адреса)?

По стандарту информация об указываемом объекте. Отсюда UB при выходе за границы объекта при применении арифметики указателей.

То есть реализация может трактовать указатели как числа и это не нарушение. Реализация может каждый объект делать в отдельном сегменте и это тоже не нарушение (например, Эльбрус в режиме безопасных вычислений так делает: каждый указатель содержит границы объекта, на который указывает, и процессор проверяет, что смещение не вышло за границы). Или реализация может считать, что из &x никак нельзя арифметикой получить &y как делает gcc и это тоже не нарушение.

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

Могут быть разные указатели с одинаковыми численными представлениями как в приведённом примере про &x + 1 и &y.

Могут быть одинаковые указатели с разными численными представлениями (например, если одна область памяти отображена на разные страницы).

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

Я коряво написал. Нельзя сравнивать числа в указателях. Меня уже поправили выше. Да, спасибо.

Я тебе про исторический пример. Никогда не кодил под дос? Я застал маленько. Там действительно можно было адресовать объекты в рамках сегмента. И в рамках абсолютных значений.

Это как пример просто. Мир не ограничивается сертифицированной виндой.

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

В принципе, термин «низкоуровневый» язык достаточно бессмысленным получается, потому что единственный ЯП, который ему соответствует, это язык ассемблера.

Есть практический смысл этого термина: низкоуровневый язык тот, конструкции которого достаточно просто переводятся в команды ассемблера целевой платформы. В этом смысле Си низкоуровневый для PDP и даже в реализациях для MSDOS. И перестал быть низкоуровневым, как только появились оптимизирующие компиляторы, опирающиеся на семантику стандарта Си, а не генерируемого без оптимизации ассемблера. И Си++ уже совсем не низкоуровневый, так как конструкции типа

auto compose =(auto f, auto g) {
    return [=](auto... x) {
        return f(g(std::forward<decltype(x)>(x)...));
    };
}; 

на ассемблер не переводятся.

monk ★★★★★
()

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

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

§ 5.10 of the C++11 standard

А причём тут C++? Тред про Си. Ты бы ещё Rust или Zig сюда притащил, лалка анскильная.

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

Уровень языка по Холстеду

Не знаю, не уверен, на мой взгляд это подходит для оценки сложности кода, а не для сравнения сложности ЯП. Вообще методология и мета-анализ - не мои сильные стороны.

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

трщ - в ц.э.в.м всё есть битовые последовательности

еси считать «адрес это число без ограничений(ака тип отличающий адресс от произовольной битовой последовательности заданной ширины)» то и получишь пхп/js стиль складыванию чего угодно с чем угодно - ибо база для сущьности быть числом иметь у себя succ

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

Он там не столько язык оценивает, сколько доступные из него библиотеки.

И странное утверждение:

С другой стороны, если язык реализации остается одним и тем же, а разрешено менять сам алгоритм, имеется другое, но похожее соотношение. В этом случае с увеличением потенциального объема V* уровень программы L уменьшится в том же отношении. Следовательно, произведение L на V* остается неизменным для любого языка.

И это произведение L на V* он определяет как уровень языка.

Но потенциальный объём у него определяется только количеством входных и выходных параметров алгоритма. L это V* / V, где V минимальный объём программы на реальном языке.

То есть, он считает, что объём любого алгоритма на реальном языке должен быть равен V* ^ 2 / λ, то есть зависеть исключительно от количества входных и выходных параметров алгоритма, что явно не соответствует действительности.

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

Адрес с которым работает программист на С это число или нет?

Не обязательно. Может быть число, может быть пара чисел (как в сегментной модели), может быть сущность, непредставимая числом (как в gcc).

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

Примерно аналогичный вопрос: графема юникода это число или нет?

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

Программист на Си видит только указатели. И линейное пространство в пределах одного объекта Си (там работает адресная арифметика). Всё остальное UB.

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

может быть сущность, непредставимая числом (как в gcc).

Ладно, адрес переменной в памяти, в программе на С это число?

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

Ладно, адрес переменной в памяти, в программе на С это число?

Нет, так как к нему неприменимы все операции, допустимые с числами.

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

Схоластический спор, что число, а что нет. Адресная арифметика это арифметические операции над указателями, значит указатель натуральное число. И линейное адресное пространство в пределах всей памяти процесса.

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

значит указатель натуральное число.

ты уплс

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

область - это «база»

индекс - вот он тоже может быть с выравниванием ибо заранее известен постояный размер адрессуемых в наихудшем случае октобит

ты сидишь в рамках одной области и счего то решил что твой указатель всегда хранится в виде байтового смещения

расширь контектс ля

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

указатель по сути это всегда пара область,индекс_в_ней

(unsigned*) 0x40021014U - валидный указатель? Где тут область и где индекс? Я просто вижу число.

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

Адресная арифметика это арифметические операции над указателями, значит указатель натуральное число

Адресная арифметика это адресная арифметика, а арифметика натуральных чисел это арифметика натуральных чисел.

Вычитание адресов в Си обычно не работает так, как вычитание натуральных чисел. А для void* в стандарте Си вообще не определена какая-либо арифметика

#include <stddef.h>
#include <stdint.h>

int main(){

	struct { double r[3] } p[4], *p1, *p2;
	p1 = p+1, p2 = p+3;
	ptrdiff_t d = p2 - p1;
	uintptr_t u1 = (uintptr_t)p1, u2 = (uintptr_t)p2;
	uintptr_t ud = u2 - u1;

	unsigned diff = (unsigned)ud - (unsigned)d;
	
	return diff;
}	
./a.out && echo $?

А вообще, конечно, в цифровой ЭВМ и посты в соцсетях, и инструкции, и данные, независимо от их смысла, представлены как большие и малые целые числа.

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

он считает, что объём любого алгоритма на реальном языке должен быть равен V* ^ 2 / λ, то есть зависеть исключительно от количества входных и выходных параметров

Холстед оценивал не алгоритмы, и даже не программы, а тексты программ на языках программирования и тексты, излагающие систему алгоритмов на естественных (английских) языках.

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

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

Адресная арифметика это арифметические операции над указателями, значит указатель натуральное число. И линейное адресное пространство в пределах всей памяти процесса.

Нет.

int x, y;

&x - &y // это UB, а если бы было "линейное адресное пространство в пределах всей памяти процесса", то было бы допустимо
monk ★★★★★
()
Ответ на: комментарий от Lusine

(unsigned*) 0x40021014U - валидный указатель? Где тут область и где индекс? Я просто вижу число.

В общем случае нет. Запусти на Intel 8086 или Эльбрусе в режиме безопасных вычислений, получишь ошибку.

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

ты сидишь в рамках одной области и счего то решил что твой указатель всегда хранится в виде байтового смещения

Он смешивает C и Linux/x86-64 assembler, в котором действительно указатели являются 64-битными числами.

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

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

&x - &y

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

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

Холстед оценивал не алгоритмы, и даже не программы, а тексты программ на языках программирования и тексты, излагающие систему алгоритмов на естественных (английских) языках.

Я с этим не спорю. И первые 2/3 статьи было более-менее адекватно.

Стало малоадекватно, когда минимальной программой для любого алгоритма он объявил аналог return имя_алгоритма(параметры_алгоритма). И совсем неадекватно, когда стал утверждать, что объём реальной программы больше этого минимального (точнее квадрата объёма минимального) в фиксированное число раз, зависящее только от языка.

Возможно неадекватен не Холстед, а автор учебника, приведённого по ссылке.

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

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

Не понял утверждение. Память может быть нелинейна, поэтому UB.

Там, где память гарантированно линейна и местный компилятор Си эту гарантию переносит на уровень указателей, это выражение вычислимо и равно 1 или -1. Потому что, если однотипные «объекты в памяти расположены линейно - т.е. друг за другом», то разница их адресов должна быть равна единице.

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

В эту методичку автор пытался впихнуть всю количественную программноинженерную мудрость для студентов.

Конечно, лучше почитать и осмыслить первоисточники.

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

Потому что, если однотипные «объекты в памяти расположены линейно - т.е. друг за другом»

Где я такое писал? Линейность в данном случае означает что виртуальные адреса монотонно возрастают от 0 до 2^32. И любой указатель может быть представлен как (uint32_t).

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