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

Что за другая модель, на что это влияет? Другие race в мультитреде?

Если хочешь подробностей, смотри тут: https://research.swtch.com/hwmm

Если на пальцах, на aarch64 могут вылезти гонки в коде, в котором на x86 их никогда не было.

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

Размер страницы 4к, как тогда работает указатель с буфером в 100 мегабайт?

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

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

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

В том числе, да. Плохое, не плохое, но так работают многие RISC-чипы: POWER так делают, SPARC так делают, даже в покойном Itanium так же было. x86 наоборот является исключением.

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

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

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

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

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

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

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

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

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

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

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

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

Т.е. std::less<T*> это ub? Напиши в комитет, а то мужики не знают.

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

Слушай, а может ты тоже считаешь, что int* в С отличается от int* в С++? Давай, я уже ко всему готов.

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

, но всё равно это числа, с которыми операции сложения вычитания ни чем не отличаются от обычных чисел.

што?

$ cat ptr-inc.c
#include <stdio.h>
#include <stdint.h>

int arr[2];

int main(void)
{
  uintptr_t i = (uintptr_t)&arr[0];
  int *p = &arr[0];

  printf("0x%llx %p\n", i + 1, p + 1);
  return 0;
}
$ gcc ptr-inc.c
$ ./a.out 
0x5b1abadfc021 0x5b1abadfc024

Смари, отличаются.

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

Притянутый пример. Очевидно, там имеется ввиду это:

(uintptr)(p+i) == ((uintptr)p) + i*sizeof(*p);

В случае когда sizeof(void*)!=sizeof(size_t) умножение в вышеуказанной формуле получается двусмысленным в зависимости от тайпкастов, но углубляться в это не обязательно, в рамках одного сегмента всё будет работать как положено, а между разными смысла делать такие вычисления всё равно особо нет.

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

Притянутый пример. Очевидно, там имеется ввиду это:

Не очевидно. @s-warus написал, что для указателей действуют те же правила, что и для чисел. И я, и ты оба показали, что нет – не действуют, для указателей действуют совершенно другие правила.

И это мы ещё до умножения и деления не дошли, которые для указателей вообще не определены.

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

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

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

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

Это можно делать только в пределах одного объекта (массив, структура).

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

void* р - указывает на начало буфера. if (*(p<<2) & 0xa0a0)

Здесь нет ни умножения, ни деления. Про UB уже написали.

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

Неа. Прочитай уже документацию наконец. И побитовые тоже нельзя, если чо. И что тебя так заклинило на malloc-ах и прочих new? Они никак не отменяют УБ-ую семантику указателей, более того, они ещё и свою добавляют. Если это плюсовое new то посмотри зачем нужна такая мутная костылина как std::launder, в том же хедере что и new лежит. А в последних стандартах ещё и std::start_lifetime_as появилось. Зачем это всё для указателей, которые -«это просто число»? А так же что можно и нельзя делать с указателем после free/delete, на лоре даже весёлый тредик был про это

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

да с костылями 86 архитектуры сложные сегменты, но всё равно это числа, с которыми операции сложения вычитания ни чем не отличаются от обычных чисел.

И как ты вычтешь b000:1234 из c000:2345 ?

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

в большинстве моих программ ds (сегмент данных 64кб) один, тоесть первая часть не менялся, а в 32 бит там еще все проще.

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

Для линейной памяти была бы арифметическая разность адресов In all other cases.

Секундочку. Мы тут полтреда выясняли, что в Си нет никаких "адресов", а есть только лишь указатели. Уже переобулись на лету или что?

Посчитай разницу между двумя соседними элементами в одном массиве.

То один объект, а не объекты.

Нет, учим букварь по си до просветления: массив - это не "один объект", а aggregate object, так же как структуры и объединения.

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

Это вообще в любой модели памяти.

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

А вот расстояние между элементами разных массивов существует только в линейной модели.

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

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

в большинстве программ ds (сегмент данных) один, тоесть первая часть не меняется

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

Конкретные компиляторы вправе доопределять UB как угодно. Например, если писать компилятор для Linux/x86-64, можно постулировать, что указатель является 64-битным числом для всех операций и что доступ по любому указателю разрешён, если не заблокирован ОС. Но тогда нельзя делать оптимизации типа

int f(x)
{
    int a = 5;
    g(x, a);
    return a + 1;
}
=>
int f(x)
{
    g(x, 5);
    return 6;
}

Потому что g может сформировать указатель на a и изменить его.

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

Переносима ли программа, если она работает на двух любых разных платформах?

Она переносима между этими двумя разными платформами, но не переносима в абсолютном смысле.

Прекрасно. Какие операции ввода / ввода может сделать программа в рамках твоей особой уличной терминологии «переносимая в абсолютном смысле»?

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

Секундочку. Мы тут полтреда выясняли, что в Си нет никаких «адресов», а есть только лишь указатели. Уже переобулись на лету или что?

Потому и нет, что память может быть не линейна.

это не «один объект», а aggregate object

Один aggregate object. Не разные же.

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

Приведите хоть одну модель памяти, где расстояние между 8-ым и 3-им элементами массива не равно 5. Расстояние в массиве определяется как разница номеров элементов массива вне зависимости от модели памяти.

А вот расстояние между элементами разных массивов существует только в линейной модели.

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

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

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

Можно вспомнить про определение линейного пространства.

Можно, но не нужно, потому что мы говорим не про математику с её роскошью бесконечных чисел, а про программную инженерию.

а) отсутствует нейтральный нулевой элемент,

Для каждого массива в рамках модели памяти си существует нулевой элемент. Более того, массив без элементов формально в си не существует (вопреки распространённой практике программирования).

Про приращение на натуральную единицу есть только, что

"А стандарт мы читаем жопой."©™

Там буквально всё необходимое написано перед английским в ASCII:

When an expression that has integer type is added to or subtracted from a pointer, the result has the
type of the pointer operand. If the pointer operand points to an element of an array object, and the
array is large enough, the result points to an element offset from the original element such that the
difference of the subscripts of the resulting and original array elements equals the integer expression.
In other words, if the expression P points to the i-th element of an array object, the expressions
(P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i + n-th
and i −n-th elements of the array object, provided they exist. 

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

Ну, слава богу.

Вообще польза таких споров в интернете в углублении понимания предмета.

Всё так.

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

Это умножение на 4.

Нет. Умножение на 4 выглядит вот так: p * 4. Ты приводишь операцию сдвига. То, что у них результат в данном случае одинаковый, не означает, что это одно и то же.

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

Один aggregate object. Не разные же.

Объекты внутри одного aggregate object у тебя уже слиплись в один или что?

Приведите хоть одну модель памяти, где расстояние между 8-ым и 3-им элементами массива не равно 5

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

Как из расстояния между элементами массива у тебя получилось наличие расстояния между элементами разных массивов (

Не надо приписывать оппоненту свои фантазии и потом с ними героически сражаться. Это как минимум не красиво.

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

Объекты внутри одного aggregate object у тебя уже слиплись в один или что?

Конечно. С точки зрения Си это указатель внутри одного объекта.

Не надо приписывать оппоненту свои фантазии и потом с ними героически сражаться. Это как минимум не красиво.

Я не приписываю. Я процитировал две написанные тобой фразы. Перечитай.

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

ты прав, но по занудствую (я не прав почти во всех случаях): некоторые платформы плохо работают с не выровнеными адресами и оптимизация может 8бит данные выровнять на 32бит.

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

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

Объекты внутри одного aggregate object у тебя уже слиплись в один или что?

Конечно.

Проветри голову.

С точки зрения Си это указатель внутри одного объекта.

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

Я процитировал две написанные тобой фразы.

Не ври, это ещё некрасивее. Я нигде не писал про разницу между двумя разными массивами.

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

Ну дык, как пел Владимир Семёнович, - «Жыраф баааальшой, яму видней!»

По сабжу: создатели Сей давно на том свете.

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

некоторые платформы плохо работают с не выровнеными адресами и оптимизация может 8бит данные выровнять на 32бит.

Это заставит распухнуть элемент массива, а не поменяет расстояние между его элементами.

Интересно, а как на таких платформах работать с malloc, если для работы с массивом из 16 значений в 8бит (то есть char) надо malloc делать на 64 байта? Или там просто sizeof(char) = 4 (вроде по стандарту нельзя)?

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

Ну что за UB-мания?

Это не UB, это ошибка компиляции.

Но если тайпкастнуть указатель в целый тип - то можно будет его << и >>, безо всяких UB. Я только не знаю зачем.

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

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

Правильный пример такой: ((uint32*)10) - ((uint32*)5).

А так, если указать однобайтовый тип, то твоё вычитание даст 0x10001111, и плевать, что это число ничего осмысленного не означает. На самом деле ему можно даже придумать применение:

copy(char *a, char *b, size s) {
  diff = b-a;
  last = a+s;
  while(a!=last) { *(a+diff) = *a; a++; }
}
firkax ★★★★★
()

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

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

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

В случае, если это разные объекты, то не может. Только если это указатели на разные элементы одного объекта-массива.

«If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression.»

Уже демонстрировали ведь: Авторы Си -- наркоманы? (комментарий)

Не ври, это ещё некрасивее. Я нигде не писал про разницу между двумя разными массивами.

Писал: Авторы Си -- наркоманы? (комментарий)

В предпоследней строке написано «А вот расстояние между элементами разных массивов существует только в линейной модели». Последняя начинается со слов «Это верно».

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

Я уже писал выше об этом: поскольку тут ничего не мешает хранить переменную 'a' в регистре (в т.ч. виртуальном), компилятор может так и сделать, и никаких «указателей на a» не будет возможно делать.

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

В том то и дело, что никакие. Ввод-вывод принципиально непереносим, он зависит либо от библиотек, либо от ОС, либо от железа. А нативного ввода-вывода в самом языке нет (если что, я в курсе что в ISO портянках про всякие printf указано, но воспринимать их всерьёз тут не следует).

особой уличной терминологии

Это не особая терминология.

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

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

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

Да в конце-концов, может быть самый умный даст уже каноническое определение, что такое указатель в С?

A pointer type may be derived from a function type, an object type, or an incomplete type, called the referenced type. A pointer type describes an object whose value provides a reference to an entity of the referenced type.

Вот тебе определение. И, кстати, пруф что указатели – не числа:

Arithmetic types and pointer types are collectively called scalar types.

Арифметические типы отдельно, указатели отдельно. И то и другое – скалярные типы, т.е. являются единым значением, в противоположность «aggregate types», т.е. массивам и структурам.

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

что такое указатель в С?

Указатель - это значение указательного типа (того, у которого самый внешний модификатор - звёздочка). Указательный тип - это такая штука, в которой можно применить операцию «звёздочка слева», создающую lvalue-объект, расположенный по адресу, указанному в двоичном представлении указателя. А так же к этой штука можно применять некоторую арифметику, уже выше разобрались какую.

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

некоторые платформы плохо работают с не выровнеными адресами и оптимизация может 8бит данные выровнять на 32бит.

Это заставит распухнуть элемент массива, а не поменяет расстояние между его элементами.

Это работает только для структур. sizeof(char array[128]) всегда будет 128.

Интересно, а как на таких платформах работать с malloc, если для работы с массивом из 16 значений в 8бит (то есть char) надо malloc делать на 64 байта?

Ни на каких, если мы говорим о стандарте Си.

Или там просто sizeof(char) = 4 (вроде по стандарту нельзя)?

sizeof(char)==1 по определению, потому что sizeof() возвращает размер в единицах char. Если хочешь биты, умножай на CHAR_BITS. Там вообще достаточно шизофреничная ситуация: комитетчики ходят вокруг да около того, что char всегда байт и что в байте 8 бит, но наглухо это стандартизировать отказываются. Но при этом если сделать char в 2 байта, это сломает другие куски стандарта.

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

А так, если указать однобайтовый тип, то твоё вычитание даст 0x10001111

Это если те два числа трактовать как одно вида x << 16 + y, но смысла в таком преобразовании нет.

copy(char *a, char *b, size s) {

a = b000:1234 
b = c000:2345
size = e000
diff = 0x10001111
last = b000:f234

Для a = b000:f000 при вышеуказанной трактовке будет a+diff == с001:0111, что явно неверно.

Правильный пример такой: ((uint32*)10) - ((uint32*)5).

С ним-то что не так? Ну зависит он от размера указателя и что?

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