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

Действительно! Посыпаю голову пеплом. Надо же, просто так не похоже на сишку, в кой-то веки ошибка компиляции вместо УБ, я то просто никогда и не пытался напрямую битики менять в указателях. Ладно, когда-то же и фиркакс должен быть прав)

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

Писал:

Ты вообще трезвый? В той ссылке нет этих букв.

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

А разные элементы одного массива у нас… У нас.. Барабанная дробь (звери в цирке замерли)… Ладно, я вижу ты реально бухой и по пьяни упёрт. По этому вот тебе на блюде с голубой каёмочкой:

6.2.5
Types
...
— An array type describes a contiguously allocated nonempty set of objects with a particular
member object type, called the element type. The element type shall be complete whenever the
array type is specified. Array types are characterized by their element type and by the number
...

Когда протрезвеешь, помедитируй над выражением "set of objects". Проверь грамматическое значение суффикса "s" в справочнике по грамматике.

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

В том то и дело, что никакие.

Бинго! В таком случае общее множество полезных "переносимых в абсолютном смысле" программ равно нулю, и этот термин таким образом лишён смысла. То есть у тебя вообще нет определения "переносимая программа на си".

А у стандарта - есть, и оно имеет (довольно ограниченную) пользу. Поэтому человечество пользуется определением из стандарта, а не твоим отсутствующим.

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

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

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

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

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

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

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

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

Это не проблема функции, это проблема юзера который в аргументе 'b' дал указатель на место в памяти, в котором до конца сегмента меньше места чем запрошено копировать байт. Да, получится битьё памяти. Если что, библиотечное bcopy тоже побьёт с такими аргументами память (хотя возможно другим способом).

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

Не так с ним то, что его нельзя корректно вычислить. Найди такую разность, которая при прибавлении к ((uin32*)5) даст ((uint32*)10).

firkax ★★★★★
()
Ответ на: комментарий от monk
  1. Хорошо, что ты не начал спорить с тем, что массив состоит из объектов.

  2. Так это твои слова, а не мои. Мои:

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

Итого:

Я нигде не писал про разницу между двумя разными массивами.

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

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

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

В таком случае общее множество полезных «переносимых в абсолютном смысле» программ равно нулю, и этот термин таким образом лишён смысла

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

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

Значит формулируем так. Указатель в С содержит виртуальный адрес и инфу о своем типе. Компилятор обрабабывая указатели генерит ассемблерный код с учета типа, куда попадают эффективные адреса, которые отображаются в физические после загрузки бинарника в память. Тогда получается &x в рантайме дает физический адрес, по которому эта х расположена. Значит с точки зрения пользователя указатель в С это физический адрес, читд.

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

А я влез со своим замечанием, чтобы помочь monk-у объяснить тебе, что у тебя нет определения, а у стандарта оно есть, и поэтому мы все пользуемся им. Кроме тебя, конечно, у тебя просто нет этого термина в словаре.

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

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

Потому что даже комитетчики (или по крайней мере некоторые из них) понимают, что компиляторы бывают равные, стандарт носит рекомендательный характер и куча реализаций с ним совместима частично. И вот, оставили место для частичной совместимости тем, у кого байт не 8 бит, или char не 1 байт. Что-то окажется точно несовместимым, а что-то всё так же можно будет применить.

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

стандарт носит рекомендательный характер

Стандарт не носит никакого характера. Это просто бумажка. Хочешь - соответствуй, хочешь - нет, дело твоё, в конце-то концов.

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

Ну, они сами то рекомендуют ему соответствовать.

Кто "они"? Где? У меня открыт стандарт в соседнем окне, там ни одной запятой про это.

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

стандарт носит рекомендательный характер и куча реализаций с ним совместима частично

Нет, не носит. Компилятор либо соответствует стандарту, либо нет.

И вот, оставили место для частичной совместимости тем, у кого байт не 8 бит, или char не 1 байт. Что-то окажется точно несовместимым, а что-то всё так же можно будет применить.

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

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

Я рад, что ты нашёл в себе мужество слиться. Если ты ещё и самозабанишься - цены тебе не будет.

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

Нет, не носит. Компилятор либо соответствует стандарту, либо нет.

Это только в каких-то идеалистических сказках так. По факту даже официально авторы компиляторов временами выкатывают списки «вот это мы поддерживаем из C89, это из C11, а это см. нашу спецификацию». Идеалистическое соответствие мало кого волнует, кроме бюрократов может быть.

современные сишные компиляторы

Это когда это комитетчиков актуальные компиляторы волновали? У них теория.

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

Не так с ним то, что его нельзя корректно вычислить. Найди такую разность, которая при прибавлении к ((uin32*)5) даст ((uint32*)10).

Можно ((uin32*)5) преобразовать в адрес 0x8, а ((uint32*)10) в 0xc. И тогда разность равна 1.

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

Это только в каких-то идеалистических сказках так. По факту даже официально авторы компиляторов временами выкатывают списки «вот это мы поддерживаем из C89, это из C11, а это см. нашу спецификацию». Идеалистическое соответствие мало кого волнует, кроме бюрократов может быть.

Нет. У стандарта есть обязательная часть и необязательные доп.фичи. В документации к компилятору приводится список поддержки этих фич.

Это когда это комитетчиков актуальные компиляторы волновали? У них теория.

Это одни и те же люди.

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

в твоей «женской»(ака бытовой) риторике очередная демонстрация ложной обратной импликации

тут жы в логику всёж выше среднего по популяции могут

вот см

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

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

ваще это прекрасно когда вона би программисты первое что херят это логику - ибо реально софты важнее чем какая-то не нужное логическое

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

Хорошо, что выводит printf( &x) на консоль?

Если ты имел в виду printf("%p", &x), то, опять же, читай стандарт:

The value of the pointer is
converted to a sequence of printing characters, in an implementation-defined
manner.

Реально рейтинг шизофреников нужно составить, как hateyoufeel говорил.

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

Указатель в С содержит виртуальный адрес и инфу о своем типе.

Сейчас указатель в первую очередь определяется типом и provenance, а конкретные операции с адресом уже следствие.

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

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

Тогда получается &x в рантайме дает физический адрес, по которому эта х расположена.

Нет. В рантайме нет &x. В рантайме есть содержимое памяти и регистров процессора.

Хорошо, что выводит printf( &x) на консоль?

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

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

слу есть двухтомный Мир Лиспа

там где-то в самом начале поясняется разница между 2 как именем и 2 как количеством которое именуется 2

вооот

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

при этом понимать что имя не есть именуемое(ну не всегда - ибо жи есть аутореферентные - см Смалиана :) )

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

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

Ну разве что Ритчи. Томпсон вон вполне себе жив и аж целый Go придумал. Да и Керниган тоже.

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

Точно, ещё Ахо и Пайка забыл. Эти тоже вроде живы ещё.

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

И Си++ уже совсем не низкоуровневый, так как конструкции типа

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

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

Как раз на ассемблер это отлично переводилось бы… если бы люнексоеды (да и все остальные) не срались так сильно из-за самомодифицирующегося кода. Ну то есть, нагенерить функцию, которая делает call g, call f и следит чтобы аргументы правильно передавались по стеку, это не то чтобы прямо rocket science.

А вот на Си такого, конечно же, не сделать, что лишний раз подтверждает, что сишечка – нихрена не низкоуровневый язык. Простая задачка, от которой у среднего сишника отвалится жопа: скопировать код функции и сохранить его в файл.

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

Ну то есть, нагенерить функцию, которая делает call g, call f и следит чтобы аргументы правильно передавались по стеку, это не то чтобы прямо rocket science.

Если у тебя на стеке только адреса g и f, то нельзя.

А вот на Си такого, конечно же, не сделать, что лишний раз подтверждает, что сишечка – нихрена не низкоуровневый язык.

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

struct function
{
  void *fpointer;
  type_desciption *params;
  int params_count;
  type_desciption *result;
}

void call(function f, params *p, result *r);

function compose(function f, function g)
{
  return compile("%1(%2(x))", f, g);
}
monk ★★★★★
()
Ответ на: комментарий от monk

Если у тебя на стеке только адреса g и f, то нельзя.

Почему нельзя-то? У тебя любой JIT буквально вот ровно это делает.

Именно описанное абзацем выше (функцию, генерирующую код функции) можно сделать.

return compile(«%1(%2(x))», f, g);

Ну, да, осталось только целый компилятор с собой притащить.

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

Ну не простая, тут это вот работает, но гарантий что тело function идёт до next_function нету никаких.

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>


int function(void)
{
    return 42;
}

int next_function(void)
{
    return 43;
}


int main(int argc, char *argv[])
{
    /* тут понятно всё */
    unsigned long size = (unsigned char*)next_function - (unsigned char *)function; /*тут может быть факап*/
    FILE * file = fopen("func.bin","wb");
    fwrite((unsigned char *)function,1,size,file);
    fflush(file);
    fclose(file);

    unsigned char * mem = aligned_alloc(4096/*по памяти*/,size);
    file = fopen("func.bin","rb");
    fread(mem,1,size,file);
    mprotect(mem, size, PROT_READ | PROT_EXEC); /*тут я гуглил - "исполняемая память си"*/
    int val = ((int (*)(void))mem)();
    printf("%d\n",val);

    return 0;
}
LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от algo

Это просто приведение типа указателя, какое расширение? И оно тут нефиг ненужное, просто до этого я в char переменные указтели сувал и приводил, потом подумал, а нафиг они нужны и просто вычел оставив приведения.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

/* тут может быть факап */

Ага. Вот именно что может. Ты сам написал почему:

гарантий что тело function идёт до next_function нету никаких.

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

Ну, может как-то можно явно задать линейность, как не знаю.
А ещё можно взять все 100500 компиляторов и хотя бы с -O0 все прогнать и глянуть где пошло в разнос, а где нет. Ибо может на бумаге нигде гарантий нет, а на деле оно везде и всегда линейно друг над дружкой, просто потому что это естественно, обработанное выше будет в выхлопе раньше. Но если взять 1 компилятор и дружить только с ним, а там это воспроизводится, то вот тебе и гарантия =) Раньше так и было порой конкретному софту, нужен конкретный компилятор, просто из за его поведения, ожидаемого. Ну, эт ладно, эт я так бубубу

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

Это так не работает. В той же OpenBSD libc перелинкуется при каждом старте системы с нуля и функции всегда идут в разном порядке по разным адресам. Если добавить сюда любовь сишников пихать каждую функцию в отдельный файл/единицу трансляции, то твой способ накрывается тазом. Но как бы да, это единственный способ без привлечения внешних костылей, так-то.

Сишечка – не низкоуровневый язык.

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

Почему нельзя-то? У тебя любой JIT буквально вот ровно это делает.

У JIT не адреса, а промежуточное представление. Как определишь, какой тип надо передавать в g, если у тебя только адрес и больше ничего.

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

Это да, сунуть туда printf или типа того и кирдык. В целом бесполезно и нельзя, в таком частном случае можно и… ну типа прикольно, ну и всё =)

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