LINUX.ORG.RU

О божественном C и введение в него через ed

 , ,


4

2

Бытие (частично) определяет.

*Какая регулярка преобразует for с телом в while с телом ?

*Какой #define макрит for в while?

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

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

к прикладному программированию относятся интерпретаторы, с «доказательством полезности» интерпретатора как языка какой-то DSL. проекции позволяют автомагически получить реализацию компилятора из референсного интерпретатора.

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

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

Для мелких микроконтроллерах прокатывает, потому что там нет внеочередного исполнения. На мощных микроконтроллерах уже не прокатывает, потому что внеочередное исполнение, а значит нужны барьеры, потому что иногда нужно писать без кэша, потому некэшируемые страницы, иногда нужно гарантировать запись кэша в память, потому CLWB и CLFLUSHOPT, а порой нужно прочитать просто одну ячейку без кэширования, потому нужны некэшируемые команды вроде MOVNTDQA на x86. Потому «volatile» решает едва ли десятую долю тех вопросов, которые нужно решать для написания ОС-и для мощного железа. «volatile» прокатывает именно для железа восмедисятых, однозадачного, без внеочередного/спекулятивного исполнения. По сути, можно сказать, что C89 устарел в момент своего выхода.

не допустимо использовать volatile в целях достижения атомарности

Кем недопустимо? Стандарт не запрещает этого делать. Стандарт в отношении «volatile» вообще мало что говорит, и не менялся в этом плане со времен C89. Лично мне очевидно, что они обосрались, но не рискуют признавать этого.

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

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

Какая разница? Он не сможет прочитать актуальное значение ни с volatile, ни без него. Разница будет лишь в частоте неактуальности, но это уже сорта говна. Я здесь пишу про многопроцессорные системы, в которых ISP может запуститься на любом ядре.

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

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

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

У компиляторов есть куча разных оптимизаций, которые он «не должен делать вообще», вроде того же strict aliasing. Это уже проблема компиляторов, а не языка.

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

Как это соотносится с моим утверждением о том, что на момент написания Си не существовало альтернатив для системного программирования?

#define альтернатива
#define системное_программирование

?

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

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

  2. гарантии системной корректности, статические и динамические разработки программной системы.

  3. наработки, идиомы, парадигмы и способы программирования как модели мышления. язык должен быть достаточно выразителен чтобы всё это было удобно реализовать на нём самом, практически применяя. не просто язык, а и «систему программирования» в том смысле, как это формулирует академик Непейвода в книге «основания программирования». похожим же образом Ершов формулирует понятие «лексикона программирования».

  4. возможность конструирования новых форм достаточной выразительности. для снижения boilerplate из пункта 3.

  5. модель выполнения язык-машины. должна гарантировать какие-то свойства из пунктов 1-4. иначе какой в ней вообще смысл, если она недалеко ушла от ассемблера и гарантий никаких?

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

  7. отсутствие ambiguity и UB, неопределённого поведения. в модели выполнения язык-машины.

  8. возможность формальной верификации стандарта благодаря 6. и выразительной теории метавычислений.

  9. простота реализации компилятора благодаря 8.

  10. отсутствие ambiguity и UB в реализациях компилятора.

  11. понятный процесс PR,RFC запросов на изменение стандарта и языка. и того, как эти фичи переносятся в стандарт.

  12. понятный процесс, что заморожено а что in the state of flux. совместимость с чем стандарт может гарантировать.

  13. полезная базовая библиотека. понятные FFI для пополнения библиотек.

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

отсюда, система большее чем сумма её частей. у неё есть эмерджментность, системный эффект, синергетика. взаимодействие которое даёт 2+2=5 а не 4. за счёт связей, архитектуры, продуманности и достаточности концепций положенных в основание идеологии языка. какой-то основополагающей идеи, резона и rationale в существовании этого, ещё одного языка.

то есть, дело не только лишь в низком уровне. а в любом, потенциально.

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

системой должно являться:

  1. язык программирования, его синтаксис и семантика, гарантии системной корректности статической и динамической. гарантии системы типов, проверяемых компилятором.

  2. рантайм реализации языка и способы его оптимизации и перенастройки.

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

  4. модель исполнения язык-машины, цель которой – обеспечивать динамические гарантии из пункта 1.

  5. освоение и понимание фич языка из пункта 3 в практике программирования, системе программирования, лексиконе программирования. определяется практикой. но некоторые до сих пор ниасиливают макросы и боятся их.

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

  7. понятные средства инфраструктурные типа пакетного менеджера, тестирования, отладки, профилирования.

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

  9. потенциальная возможность работы как на сверхнизком уровне, так и на сверхвысоком.

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

В каком месте это полезно для системного программирования?

что конкретно может гарантировать компилятор и когда он должен это делать?

каковы контракты в терминах контрактного программирования между:

  1. языком и компилятором

  2. системой типов и статической/динамической корректностью разрабатываемой системы

  3. возможности оптимизации «достаточно умным компилятором». что он должен знать для этой возможности? а чего не должен?

  4. оговорки и исключения, когда это не применимо.

ну или в терминах rule-based, как вообще можно выразить какие-то гарантии?

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

гарантируют чтение именно из оперативной памяти, а не из кэша

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

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

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

Стандарт не запрещает этого делать.

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

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

30 лет назад по этой теме был отличный USENET-пост за авторством Doug Gwyn:

Чтобы привести определенный пример, драйверы устройств UNIX почти всегда кодируются полностью на C, и на PDP-11 и подобных архитектурах с привязкой к памяти ввода/вывода, некоторые регистры выполняют различные действия для операций «чтение байта», «чтение слова», «запись байта», «запись слова», «чтение-модификация-запись», или другие вариации операций по доступу к шине памяти. Попытки получить генерацию правильно работающего машинного кода драйвера на C было довольно затруднительным, с появлением трудно обнаруживаемых ошибок. Если используются компиляторы не от Ritchie, включение оптимизации также изменяло бы поведение программы. Как минимум одна версия компилятора UNIC (UNIX Portable C Compiler, PCC) имела специальный хак для распознавания конструкций наподобие следующей:

((struct xxx *)0177450)->zzz

Подобное выражение было бы потенциальной ссылкой на пространство ввода/вывода (регистры периферийного устройства), и в этом месте следует избегать чрезмерной оптимизации (когда константа попадает в область адресного пространства Unibus I/O). X3J11 решил, что проблему надо решать, и ввел «volatile» чтобы устранить необходимость в этих хаках. Однако хотя было рекомендовано для реализаций удовлетворять минимально возможной ширине для данных, квалифицированных как volatile, и было решено непрактичным настаивать на внедрении этого требования в каждой реализации; так что конструкторам была предоставлена некоторая свобода в разработке компиляторов.

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

#define альтернатива
#define системное_программирование

«Систе́мное програ́ммное обеспе́чение — это комплекс программ, которые обеспечивают управление компонентами компьютерной системы, такими как процессор, оперативная память, устройства ввода-вывода, сетевое оборудование, выступая как «межслойный интерфейс», с одной стороны которого аппаратура, а с другой - приложения пользователя. В отличие от прикладного программного обеспечения, системное не решает конкретные прикладные задачи, а лишь обеспечивает работу других программ, управляет аппаратными ресурсами вычислительной системы и т.д.»

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

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

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

Ты так и не ответил, что будет вместо дерева. Если не держать связи между элементами, то программа, анализирующая эти структуры, сама будет вынуждена достраивать эти связи, и в итоге мы все равно приходим к дереву, как к естественной интерпретации текстовой формы кода. Вот питон и TCC не строят дерево. Что между ними общего? Они не делают оптимизации кода.

source {
    exprs  []expr
    decls  []decl
    entry  decl
    scopes []scope
}

typdecl {
    scope      scope
    name       string
    underlying typ
}

funcdecl {
    scope scope
    name  string
    args  argsexpr
    reply argsexpr
    exprs []*expr
}

Единственная древовидность у scope. И то, его можно складывать в стек в момент parsing-а в стек и разрешать namespase по месту. И после он уже не нужен вообще. Особенно в Си с его линейностью. Для других случаев получаем хэш-таблицу имя->decl. Например, если типы декларированы внизу файла и на момент разбора не ясны. Вторым проходом можно от неё (хэш-таблицы) избавиться.

В итоге получаем набор exprs, общий. Набор decls, общий. Функция состоит из набора exprs. И собственно expr может быть списком expr. Что так же можно принять за древо.

Упростив всё получаем

source {
   exprs []expr
   decls []decl
}

funcdecl {
    exprs []*expr
}

Третьим проходом каждая функция перестраивается в SSA.

source {
    decls []decl
}

ssafunc {
   exprs []ssaexpr
}

Функция вырождается в массив. Дерево в массив массивов.

При той или иной сложности языка можно сделать всё за один проход манипулируя стеком.

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

В каком месте это полезно для системного программирования?

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

Всё просто: если я пишу в ячейку памяти, то я пишу в ячейку памяти. Если я делаю запрос по сети, то компилятор не будет думать чего-то вроде «а может тебе не нужно делать этот запрос, опомнись?», или «вы действительно хотите удалить этот файл?». Если кэширование есть, то оно более-менее явно описано, как это сделано в printf или открытии файла без O_SYNC.

Все глобальные и статические переменные в Си по стандарту (плюс минимальные додумки) ведут себя именно как volatile, и стандарт описывает volatile через «программа должна вести себя максимально близко к абстрактной машине». То, что делают оптимизаторы - это недопустимые вольности, срезание углов, которое ломает программу в угоду увеличения скорости ее выполенния. Примерно как со strict aliasing, с которым отваливается целая куча годного софта, вроде ядра линукса, gnome, и вообще всего gtk-based софта.

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

Тебе не кажется, что это проблемы железа, а не языка? Язык не должен выпрямлять кривизну железа

А такое нынче всё железо. Если Си не поддерживает современное железо, то он просто не нужен, поскольку у него остается ноль сфер применения.

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

Стандарт не запрещает этого делать.

стандарты также не запрещают голой жопой сидеть на раскаленной сковородке, это да, тут не возразишь

Мы снова возвращаемся к тому, с чего я начал: стандарты - это пустой марание бумаги и протирание штанов, всё решает именно фактический неформальный стандарт.

Если используются компиляторы не от Ritchie, включение оптимизации также изменяло бы поведение программы

Что доказывает, что эта оптимизация некорректна.

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

А такое нынче всё железо.

Нынче ширпотребное железо - это нагромождение костылей ради ускорения «однозадачности» в виде кешей, беспорядочных конвееров… Язык Си не расчитан на многозадачность. Вроде всё должно быть чики-пуки. Но…

… Си не поддерживает современное железо, то он просто не нужен, поскольку у него остается ноль сфер применения.

Как так получилось? Почему железо и софт идут навстречу друг другу, но у них не получается встретиться? Проблема в «месте встречи» или в том что никто не знает где должно быть это «место встречи»?

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

Третьим проходом каждая функция перестраивается в SSA

И теряется часть информации о коде, главным образом о составных структурах, которые просто так в SSA никак не преобразовываются. По этой причине GCC держит SSA в виде дерева, не разворачивая его в плоскую форму, а LLVM в своем IR содержит вспомогательные инструкции, которые фронтэнд генерирует для помощи бэкэнду, потому что иначе бэкэнд не сможет разобратся в смысле кода:
https://llvm.org/docs/Frontend/PerformanceTips.html

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

Что доказывает, что эта оптимизация некорректна.

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

а стандарт всего лишь закрепляет такой подход, избавляет от хаков вроде ((struct xxx *)0177450)->zzz, тем самым снижая энтропию, хаос, предотвращает кариес, перхоть и выпадение волос у отдельно взятой категории инженеров-программистов.

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

Нынче ширпотребное железо - это нагромождение костылей ради ускорения «однозадачности» в виде кешей, беспорядочных конвееров

Да вполне себе порядочные конвееры. Хочешь писать многопотоки? Пардон, но ты сам будешь плеваться от того, что у тебя потоки в разнобой берут ресурсы и изменяют данные. Костыльным скорее является конкретно архитектура x86, которая в том числе из-за Си не получила «weak ordering», хотя большинство других процессоров имеют поддержку «weak ordering», просто некоторые, вроде PowerPC, имеют опциональную поддержку «strong ordering».

В принципе, отказ от x86 видится вполне реальным в ближайшем будущем на фоне потухания Windows и восхождения новой звезды, ARM-Android-iOS.

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

Что «чики-пуки»? Да, он не расчитан на многозадачность. UNIX вообще не расчитан на многозадачность. Все эти технологии устарели в 80-х. Но IT - это очень инертная, консервативная сфера, потому менять никто ничего не будет, и на этом фоне нужно жить. Нынче прогресс и вовсе стал колом, потому что современные компьютеры, по сути, это просто компьютеры из девяностых-нулевых, но которые умещаются в карман. Всё. Софт не стал умнее, софт не стал надежнее, просто комп теперь стал меньше и потребляет меньше энергии.

Почему железо и софт идут навстречу друг другу, но у них не получается встретиться?

Да никуда они не идут. Systems Software Research is Irrelevant, помнишь?

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

компилятор всегда оптимизирует исходя из свойств абстрактной машины С. всегда

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

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

«Не так» - это как? Ячейка памяти может внезапно перестать быть ячейкой памяти, что ли?

ее состояние определяется за пределами абстрактной машины

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

стандарт всего лишь закрепляет такой подход, избавляет от хаков вроде ((struct xxx *)0177450)->zzz

Ты перевернул всё верх ногами. Грязными хаками были оптимизирующие компиляторы, которые генерировали некорректный код. Чтобы сделать этот код корректным, применяли костыли, то есть, наспех состряпанные решения. Комитет не решил проблему - он просто стандартизировал костыли, подпирающие грязные хаки.

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

Хочешь писать многопотоки?

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

А ширпотребный CPU - это именно костыли, ни нормального однопотока, ни нормального многопотока. И программирование многопотока на CPU на практике - это комбинаторика костылей («weak ordering», «strong ordering» - без разницы). И в долгосрочной перспективе такой опыт костыляния бесполезен.

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

отказ от x86 видится вполне реальным в ближайшем будущем на фоне потухания Windows и восхождения новой звезды, ARM-Android-iOS.

Шило на мыло.

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

Ячейка памяти может внезапно перестать быть ячейкой памяти, что ли?

именно.

у тебя ровненький блок памяти, который идеально ложится на линейную модель памяти абстрактной машины С. и все было бы хорошо, но двойное слово по адресу 0177450, которое казалось бы ровно такое же как и все соседние области, на самом деле перезаписывается извне, реальной машиной.

поэтому конструкция вида

int c = 0;
while(i) c++;

которая выглядит как бессмысленный и беспощадный кусок кода, внезапно обретает смысл если i - волатильная переменная, значение которой определяет реальная машина. вот сбросит реальная машина значение переменной в 0 - и мы выскочим из цикла.

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

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

Более менее настоящий многопоток в современном ширпотребе - это GPU, где массив «вычислителей» напрямую работает с массивом данных без кешей

Неа. Даже в ранних видюхах были read-only кэши текстур. В современных видюхах ситуация приближается к CPU, только не хватает еще L3 кэша:
https://en.wikipedia.org/wiki/Maxwell_(microarchitecture)

individual units, each containing 256KB of L2 cache and 8 ROPs

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

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

То, что делают оптимизаторы - это недопустимые вольности, срезание углов, которое ломает программу в угоду увеличения скорости ее выполенния. Примерно как со strict aliasing

нарушения strict aliasing это срезание углов программистом и недопустимые вольности с его стороны. если девелопер срезал острые углы, а потом сообщил компилятору что он этого не делал, то кто виноват в том, что код превратился в кусок нерабочего говна?

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

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

А я пишу о том, что оно перезаписывается изнутри.

поэтому конструкция вида
int c = 0;
while(i) c++;
которая выглядит как бессмысленный и беспощадный кусок кода, внезапно обретает смысл если i - волатильная переменная, значение которой определяет реальная машина. вот сбросит реальная машина значение переменной в 0 - и мы выскочим из цикла

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

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

нарушения strict aliasing это срезание углов программистом и недопустимые вольности с его стороны. если девелопер срезал острые углы, а потом сообщил компилятору что он этого не делал, то кто виноват в том, что код превратился в кусок нерабочего говна?

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

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

Прикинь, в паскале этот код работает без каких-либо модификаторов и с любыми опциями оптимизации

работать и работать правильно - это разные вещи.

temp = *pRegisterA;
*pRegisterA = *pRegisterB;
*pRegisterB = temp;

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

а если код должен делать перестановку содержимого между двумя периферийными регистрами MMR?

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

Ты для начал попробуй найди в стандарте определения strict aliasing

dereferencing a pointer that aliases an object that is not of a compatible type or one of the other types allowed by C 2011 6.5 paragraph 7 is undefined behavior.

The types that C 2011 6.5 7 allows an lvalue to access are:

  • a type compatible with the effective type of the object,

  • a qualified version of a type compatible with the effective type of the object,

  • a type that is the signed or unsigned type corresponding to the effective type of the object,

  • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,

  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

  • a character type.

olelookoe ★★★
()

Ну что, кто-нибудь всё-таки ответит, как регулярками парсить контекстно-свободный Си?

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

read-only кэши текстур

Это не серьезно.

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

в GPU кэши имеют продвинутую поддержку синхронизации и атомарных операций

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

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

как регулярками парсить контекстно-свободный Си?

В перле такие «регулярки» с откатами, что, думаю, контекстозависмый Си распарсит.

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

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

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

что сделает оптимизирующий компилятор, если эти переменные нигде больше не используются?

Давай начнем с того, как вообще компилятор определит, используются ли эти переменные или нет. Если они экспортируются, то на уровне отдельного модуля невозможно определить, используются ли где-то еще эти переменные или нет. Если переменные объявлены как static (было уже в C89) и их адрес не используется в нестатичных функциях, то можно эти переменные/функции выкинуть. Если же функция экспортируется и ей передается неизвестный указатель, ровно как и обратная ситуация, когда берется указатель на статичную переменную и передается куда-то вовне - мы опять же в принципе не можем определить используемость/неиспользуемость переменной на уровне отдельного модуля.

Статичные функции, к слову, еще и прекрасно инлайнятся:
https://gcc.gnu.org/onlinedocs/gcc/Inline.html

When a function is both inline and static, if all calls to the function are integrated into the caller, and the function’s address is never used, then the function’s own assembler code is never referenced. In this case, GCC does not actually output assembler code for the function

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

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

dereferencing a pointer that aliases an object that is not of a compatible type or one of the other types allowed by C 2011 6.5 paragraph 7 is undefined behavior

Да, только стандарт не может ответить на элементарный вопрос: доступ к полю структуры через оператор "->" — это вообще что в контексте 6.5 paragraph 7? А если потом еще и взять адрес этого поля?

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

Синтаксис самих «регулярок» – нет, но они годятся только для разбора регулярных грамматик. Насколько знаю в них нет ни какой-то промежуточной памяти, ни рекурсий. Их можно использовать максимум на этапе лексического анализа. Правильно распарсить сишный for и преобразовать его в while им точно не под силу.

Хотя я видал в шестом пёрле какие-то грамматики намутили, но я с ним плохо знаком, будем считать что его нет.

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

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

давай начнем с того, что не будем усложнять.

int main(){
...
temp = *pRegisterA;
*pRegisterA = *pRegisterB;
*pRegisterB = temp;
...

}

расскажи мне, как без модификатора доступа volatile ты собираешься сказать компилятору что этот кусок кода на что-то влияет и оптимизации тут не нужны?

напоминаю, эти переменные больше нигде не используются.

Компилятор выкинул операции, которые обязательны в работе программы - на каком таком основании он это сделал?

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

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

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

расскажи мне, как без модификатора доступа volatile ты собираешься сказать компилятору что этот кусок кода на что-то влияет и оптимизации тут не нужны?

static int RegisterA;
static int RegisterB;

int main(){
...
int *pRegisterA = &RegisterA;
int *pRegisterB = &RegisterB;
int temp;
temp = *pRegisterA;
*pRegisterA = *pRegisterB;
*pRegisterB = temp;
...

}

PS: я сделал наоборот - я указал компилятору, что этот кусок кода можно оптимизировать. Еще раз повторюсь: оптимизации компиляторов без volatile некорректны, они производят программу, отличную от описанной в исходном коде.

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

напоминаю, эти переменные больше нигде не используются

Это уже вопрос скорее к писателям говнософта: почему вы написали переменные, а они нигде не используются? Паскали обычно выдают предупреждение.

основываясь на чем можно принять решение об обязательности этих операций?

Например, на том основании, что кодер их написал. Этого недостаточно? Зачем писать код, который не будет выполняться?

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

А у нас вся оптимизация заканчивается на вырезании неиспользуемого кода?

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

доступ к полю структуры через оператор «->»

это доступ к области памяти определенного размера. размер области памяти определен на этапе объявления типа данных поля структуры.

каст типа данных ( и последующая запись) на другой тип данных - это UB в контексте 6.5 paragraph 7, за исключением случаев, явно перечисленных 6.5 paragraph 7

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

это доступ к области памяти определенного размера. размер области памяти определен на этапе объявления типа данных поля структуры

Но программа не читает всю структуру. Более того, программа вообще ничего не читает из структуры, если берется один лишь адрес. И вот мы уже приходим к тому, что я могу превращать указатели на структуры неограничено (что описано в стандарте, к слову), а потом мне достаточно лишь брать в этих разных структурах указатель на поле с тем же типом, независимо от его позиции в структуре, и работать с ним в полном соответствии с 6.5 paragraph 7.

каст типа данных ( и последующая запись) на другой тип данных

Вот только не надо мешать одну с другим. Касты указателей почти не ограничены:
6.3.2.3. Параграф 7

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.

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

почему вы написали переменные, а они нигде не используются?

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

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

int loop_add3 (int x)
{
   int i;
   for (i=0; i < 3; i++)
      x++;
   return x;
}

после компиляции получим что-то вроде

loop_add3:
   movl  4(%esp), %eax
   addl  $3, %eax
   ret

семантика. человеку проще писать и понимать семантически обоснованный код. хотя компилятору пофиг как называются функции, переменные и как код организован. пиши себе хоть int i357(xyz, zzz, aa2) и далее, сплошной простыней без разбиения на модули. задача компилятора из человеко-понятного сделать машино-эффективное.

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

Вот только не надо мешать одну с другим. Касты указателей почти не ограничены: 6.3.2.3. Параграф 7

угу. только обращение к полю данных оператором -> это обращение к значению поля, а не к указателю на него.

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

Эм, а return x + 3 разве менее человеко-понятно?

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

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

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

Убойная логика. «main()» дальше в коде нигде не используется — давайте его соптимизируем?

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

int loop_add3 (int x)
{
int i;
for (i=0; i < 3; i++)
x++;
return x;
}

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

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

угу. только обращение к полю данных оператором -> это обращение к значению поля, а не к указателю на него

Но взятие указателя на поле ведь уже не является доступом, не так ли? И вот мы получили указатель на черт знает что в полном соответствии со стандартом.

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

Но взятие указателя на поле ведь уже не является доступом, не так ли? И вот мы получили указатель на черт знает что в полном соответствии со стандартом.

что не имеет никакого отношения к strict aliasing

olelookoe ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.