LINUX.ORG.RU

История изменений

Исправление byko3y, (текущая версия) :

это как «внутри С++ есть какой-то простой язык, который пытается выбраться наружу». только он почти как метапрог – никто не понимает, что это такое. потому что у каждого свой метаязык, своё подмножество этого «простого» языка.
фактический стандарт. ну давай. это – ad hoc бюрократизация, кодификация сложившейся глупости и предубеждений или всё-таки нечто продуманное, на основании принципов?

Объясняю конкретно. C89 в основном ввел понятия прототипов функции и ключевых слов «volatile» и «const». По сути, прототипы функций - единственная полезная фича, несмотря на минимальные подводные камни:
https://gcc.gnu.org/onlinedocs/gcc/Function-Prototypes.html#Function-Prototypes
Тут скорее вопрос к K&R - почему язык столько лет просуществовал без типов формальных аргументов функции?

Далее, const - совершенно бесполезный модификатор, если учесть, что касты указателей неограничены, а преимуществ для оптимизиторов он не дает:
https://theartofmachinery.com/2019/08/12/c_const_isnt_for_performance.html

А теперь объясняю про проблемы volatile. В x86 по канону применяется барьер release для абсолютно всех операций, но другие архитектуры процессоров (SPARC, POWER, PowerPC, MIPS) таких гарантий не дают. Однако, даже в x86 порядок чтений по умолчанию не гарантируется. Внеочередное выполнение было известно уже в 1964 году, так что отмаза плана «ну мы не знали ничего про внеочередное исполнение» не прокатывает:
https://en.wikipedia.org/wiki/IBM_System/360_Model_91
https://en.wikipedia.org/wiki/POWER1 - 1990
Особенно эта отмаза не прокатывает в C99.

И что же мы теперь получили с volatile: программа будет читать значение из ячейки в памяти, но операции чтения могут быть переставлены местами, и программа прочитает «тухляк», увидит новое значение в первой строчке своего выполнения, но старое значение во второй строчке, что может адово поджигать пуканы, например, любителям писать синглетоны.
На этот случай в комитетах действует правило: комитет не может обосраться дважды. Потому «исправления» предыдущих комитетов никогда не исправляются во второй раз. В итоге только в C11 добавили атомики:
https://en.cppreference.com/w/c/atomic/atomic_init
https://en.cppreference.com/w/c/atomic/atomic_load
Здесь я просто хочу, чтобы вы запомнили аргумент: volatile не подходит для многопоточных приложений. А значит зачем он вообще нужен? Правильно, для оптимизации кода, чтобы глобальные переменные стали как бы не глобальными, а глобально-кэшированными.

Теперь перейдем к вопросу о том, что же должен был сделать комитет вместо volatile. Для этого зададимся вопросом «почему в паскале нет проблемы volatile, а в Си, Фортране, и Коболе - есть?». А потому что Си сам толкает программиста на использование глобальных переменных, в том числе в виде локальных переменных с модификатором «static». Естественно, когда создатели компиляторов видят это дело, то они хватаются за голову «у вас все переменные глобальные, но используете вы их локально. Как нам это говно оптимизировать?». И вместо того, чтобы послать лесом весь устаревший софт (который и дальше будет читать/писать глобальную память) и сделать новые конструкции для описания локальных переменных локальными и потому оптимизируемыми - бездарные дауны из комитета добавили volatile, действуя по методу наименьшего сопротивления в комитетном бурлении говен.

На этом фоне, к слову, выскочил C++, который имел фичу локального контекста, и это взлетело, да еще и как взлетело.

Давайте рассмотрим типичную такую задачу: взять контейнер, содержащий какие-то вещи, пройтись по каждому его элементу и применить к нему операцию. Если у вас задача простая, вроде «увеличить каждое значение на один», то можно обойтись простым указателем на функцию. С более сложными случаями такое не прокатит, например: взять элементы, у которых со значение ключа содержится в некоем массиве-аргументе, и применить к этим элементам замену слов по другому аргументу-словарю. K&R грят, что делать нужно вот так:

typedef struct {
  char *original;
  char *new;
} dict_element;

static int funcname_dict_count;
static dict_element *funcname_dict;
static int funcname_key_count;
static int *funcname_keys;

void funcname(container_element *element) {
  if (key_in_array(element->key, funcname_keys, funcname_key_count))
  ...
}

int main() {
  ...
  fill_keys(&funcname_keys, &funcname_key_count);
  fill_dict(&funcname_dict, &funcname_dict_count);
  container_for_each(container, &funcname);
  ...
}

В современном коде, вроде ядра линукса или Win API, это бы сделали как-то так:

typedef struct {
  char *original;
  char *new;
} dict_element;

typedef struct {
  int key_count;
  int *keys;
  int dict_count;
  dict_element *dict;
} funcname_args;

void funcname(container_element *element, funcname_args *args) {
  if (key_in_array(element->key, args->keys, args->key_count))
  ...
}

int main() {
  ...
  funcname_args args;
  fill_keys(&args->keys, &args->key_count);
  fill_dict(&args->dict, &args->dict_count);
  container_for_each(container, (container_for_each_function)&funcname, args);
  ...
}

Для таких задач в C++11 наконец придумали замыкания:

typedef struct {
  char *original;
  char *new;
} dict_element;

int main() {
  ...
  static int dict_count;
  static dict_element *dict;
  static int key_count;
  static int *keys;
  fill_keys(&keys, &key_count);
  fill_dict(&dict, &dict_count);
  auto funcname = [keys, dict] (element) {
    if (key_in_array(element->key, keys, key_count))
    ...
  };
  container_for_each(container, funcname);
  ...
}

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

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

Я надеюсь, что я вас убедил в том, что volatile - это бесполезный мусор в языке. В сухом остатке мы имеем:
- C89 привнес в язык прототипы функций, как единственную полезную новую фичу;
- Керниган и Ритчи ничерта не соображали и не соображают в проектировании языков программирования.

Теперь вернемся к исходному вопросу: зачем вообще комитеты существуют, потребляют кислород, и производят говно? Комитет не дал Си практически ничего, он не сделал язык лучше, он просто взял уже имеющиеся говеные практики и формализовал их.

Исходная версия byko3y, :

это как «внутри С++ есть какой-то простой язык, который пытается выбраться наружу». только он почти как метапрог – никто не понимает, что это такое. потому что у каждого свой метаязык, своё подмножество этого «простого» языка.
фактический стандарт. ну давай. это – ad hoc бюрократизация, кодификация сложившейся глупости и предубеждений или всё-таки нечто продуманное, на основании принципов?

Объясняю конкретно. C89 в основном ввел понятия прототипов функции и ключевых слов «volatile» и «const». По сути, прототипы функций - единственная полезная фича, несмотря на минимальные подводные камни:
https://gcc.gnu.org/onlinedocs/gcc/Function-Prototypes.html#Function-Prototypes
Тут скорее вопрос к K&R - почему язык столько лет просуществовал без типов формальных аргументов функции?

Далее, const - совершенно бесполезный модификатор, если учесть, что касты указателей неограничены, а преимуществ для оптимизиторов он не дает:
https://theartofmachinery.com/2019/08/12/c_const_isnt_for_performance.html

А теперь объясняю про проблемы volatile. В x86 по канону применяется барьер release для абсолютно всех операций, но другие архитектуры процессоров (SPARC, POWER, PowerPC, MIPS) таких гарантий не дают. Однако, даже в x86 порядок чтений по умолчанию не гарантируется. Внеочередное выполнение было известно уже в 1964 году, так что отмаза плана «ну мы не знали ничего про внеочередное исполнение» не прокатывает:
https://en.wikipedia.org/wiki/IBM_System/360_Model_91
https://en.wikipedia.org/wiki/POWER1 - 1990
Особенно эта отмаза не прокатывает в C99.

И что же мы теперь получили с volatile: программа будет читать значение из ячейки в памяти, но операции чтения могут быть переставлены местами, и программа прочитает «тухляк», увидит новое значение в первой строчке своего выполнения, но старое значение во второй строчке, что может адово поджигать пуканы, например, любителям писать синглетоны.
На этот случай в комитетах действует правило: комитет не может обосраться дважды. Потому «исправления» предыдущих комитетов никогда не исправляются во второй раз. В итоге только в C11 добавили атомики:
https://en.cppreference.com/w/c/atomic/atomic_init
https://en.cppreference.com/w/c/atomic/atomic_load
Здесь я просто хочу, чтобы вы запомнили аргумент: volatile не подходит для многопоточных приложений. А значит зачем он вообще нужен? Правильно, для оптимизации кода, чтобы глобальные переменные стали как бы не глобальными, а глобально-кэшированными.

Теперь перейдем к вопросу о том, что же должен был сделать комитет вместо volatile. Для этого зададимся вопросом «почему в паскале нет проблемы volatile, а в Си, Фортране, и Коболе - есть?». А потому что Си сам толкает программиста на использование глобальных переменных, в том числе в виде локальных переменных с модификатором «static». Естественно, когда создатели компиляторов видят это дело, то они хватаются за голову «у вас все переменные глобальные, но используете вы их локально. Как нам это говно оптимизировать?». И вместо того, чтобы послать лесом весь устаревший софт (который и дальше будет читать/писать глобальную память) и сделать новые конструкции для описания локальных переменных локальными и потому оптимизируемыми - бездарные дауны из комитета добавили volatile, действуя по методу наименьшего сопротивления в комитетном бурлении говен.

На этом фоне, к слову, выскочил C++, который имел фичу локального контекста, и это взлетело, да еще и как взлетело.

Давайте рассмотрим типичную такую задачу: взять контейнер, содержащий какие-то вещи, пройтись по каждому его элементу и применить к нему операцию. Если у вас задача простая, вроде «увеличить каждое значение на один», то можно обойтись простым указателем на функцию. С более сложными случаями такое не прокатит, например: взять элементы, у которых со значение ключа содержится в некоем массиве-аргументе, и применить к этим элементам замену слов по другому аргументу-словарю. K&R грят, что делать нужно вот так:

typedef struct {
  char *original;
  char *new;
} dict_element;

static int funcname_dict_count;
static dict_element *funcname_dict;
static int funcname_key_count;
static int *funcname_keys;

void funcname(container_element *element) {
  if (key_in_array(element->key, funcname_keys, funcname_key_count))
  ...
}

int main() {
  ...
  fill_keys(&funcname_keys, &funcname_key_count);
  fill_dict(&funcname_dict, &funcname_dict_count);
  container_for_each(container, &funcname);
  ...
}

В современном коде, вроде ядра линукса или Win API, это бы сделали как-то так:

typedef struct {
  char *original;
  char *new;
} dict_element;

typedef struct {
  int key_count;
  int *keys;
  int dict_count;
  dict_element *dict;
} funcname_args;

void funcname(container_element *element, funcname_args *args) {
  if (key_in_array(element->key, args->keys, args->key_count))
  ...
}

int main() {
  ...
  funcname_args args;
  fill_keys(&args->keys, &args->key_count);
  fill_dict(&args->dict, &args->dict_count);
  container_for_each(container, (container_for_each_function)&funcname, args);
  ...
}

Для таких задач в C++11 наконец придумали замыкания:

typedef struct {
  char *original;
  char *new;
} dict_element;

int main() {
  ...
  static int dict_count;
  static dict_element *dict;
  static int key_count;
  static int *keys;
  fill_keys(&keys, &key_count);
  fill_dict(&dict, &dict_count);
  auto funcname = [keys, dict] (element) {
    if (key_in_array(element->key, keys, key_count))
    ...
  };
  container_for_each(container, funcname);
  ...
}

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

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

Я надеюсь, что я вас убедил в том, что volatile - это бесполезный мусор в языке. В сухом остатке мы имеем:
- C89 привнес в язык прототипы функций, как единственную полезную новую. фичу;
- Керниган и Ритчи ничерта не соображали и не соображают в проектировании языков программирования.

Теперь вернемся к исходному вопросу: зачем вообще комитеты существуют, потребляют кислород, и производят говно? Комитет не дал Си практически ничего, он не сделал язык лучше, он просто взял уже имеющиеся говеные практики и формализовал их.