2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу Пред.  1, 2, 3, 4  След.
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 15:26 
Заслуженный участник
Аватара пользователя


16/07/14
9146
Цюрих
MGM в сообщении #1570064 писал(а):
И чего делать?
Разбираться, что происходит. Для начала - проверить, что есть способ устойчиво воспроизвести проблему. Попробовать собрать код с санитайзерами (https://learn.microsoft.com/en-us/cpp/s ... w=msvc-170).
MGM в сообщении #1570064 писал(а):
То есть перед выполнением inline функции tree() значение st одно, а сразу же перед певым оператором внутри функции другое ( согласно cout << st).
Где-то в отладчике есть функция "отслеживать значение переменной" и останавливаться в месте, где значение меняется.
MGM в сообщении #1570064 писал(а):
Иакое впечатление, что компилятор оптимизируя создает какие-то свои представления о значениях параметра
Почти наверняка компилятор всё делает правильно, а ошибка в коде. Скорее всего - проезд по памяти, и где-то выход за границы массива или использование уничтоженного объекта.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 17:02 
Аватара пользователя


05/06/08
477
TheRuinedMap в сообщении #1569769 писал(а):

"...вместо очевидного true (!=0)..." Вы продолжаете пребывать в плену каких-то загадочных инопланетных теорий о том, что такое true... ¯\_(ツ)_/¯


Или Вы продолжаете приписывать мне ложное недопонимание простых истин.
Я живу в мире С XX века с последовательной компиляцией.
Для меня очевидно, что код:
Код:
bool choice = funk25();
cout << choice << endl;
if(choice){
cout << choice << endl;
}
else {
cout << choice << endl;
}

должен выдавать на консоль только два возможных сообщения:
true
true


или

false
false


В этом мире было запрещено всталять в код манипуляции с переменной choice межу строкой вывода:
cout << choice << endl;
и оператором ветвления
if(choice){};
Даже если есть в коде баг, который затирает память, или изменяет значение указателя или переменной, в силу ошибки кода, мне непонятна избирательность такого бага.
Ведь все остальное работает нормально.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 17:20 
Аватара пользователя


28/10/21
100
MGM в сообщении #1570077 писал(а):
Или Вы продолжаете приписывать мне ложное недопонимание простых истин.
Я живу в мире С XX века с последовательной компиляцией.
Для меня очевидно, что код:
должен выдавать на консоль только два возможных сообщения:
true
true

или
false
false



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

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

MGM в сообщении #1570077 писал(а):
В этом мире было запрещено всталять в код манипуляции с переменной choice межу строкой вывода:
cout << choice << endl;
и оператором ветвления
if(choice){};


Для того, чтобы код с неопределенным поведением демонстрировал это самое неопределенное поведение, совсем не нужны никакие "манипуляции с переменной choice". Откуда вы такое взяли?

MGM в сообщении #1570077 писал(а):
Даже если есть в коде баг, который затирает память, или изменяет значение указателя или переменной, в силу ошибки кода, мне непонятна избирательность такого бага.


Еще раз: как только в вашей программе возникает bool переменная с "разрушенным" значением, дальнейшее поведение разнообразных ветвлений по этой переменной запросто может быть непредсказуемым. Для этого не нужно ничего затирать и не нужно менять никакие значения. Откуда вы такое взяли?

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 17:26 
Заслуженный участник
Аватара пользователя


16/07/14
9146
Цюрих
MGM в сообщении #1570077 писал(а):
в мире С XX века
C89 к XX веку относится?
Стандарт C89 писал(а):
Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results. to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message). to terminating a translation or execution (with the issuance of a diagnostic message)

MGM в сообщении #1570077 писал(а):
с последовательной компиляцией
Никакого отношения к последовательности компиляции проблема не имеет.
MGM в сообщении #1570077 писал(а):
Для меня очевидно
Ну вот то, что вам очевидно совершенно неверное утверждение как раз и является "недопониманием простой истины".
MGM в сообщении #1570077 писал(а):
мне непонятна избирательность такого бага
Компилятор про код с таким багом нет гарантирует вообще ничего. Имеет полное право вставить туда форматирование диска, или запуск NetHack (и последнее происходило) на практике. Это вы думаете, что каждая строчка должна транслироваться в ассемблерные инструкции по интуитивным правилам. На самом деле не должна.
См. так же: What very C programmer should know about Undefined Behavior.
Вам эта ситуация может не нравиться, вы можете сколько угодно считать такое поведение глупостью, но компилятор работает именно так. И, согласно стандарту, имеет право. Багрепорты писать авторам компилятора в таких случаях бесполезно - ответ будет "works as intended". Так что у вас три выхода: взять другой язык, сделать "С++ им. MGM", или писать в соответствии со стандартом.

(Оффтоп)

А откуда в мире С взялся cout?

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

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 17:29 
Аватара пользователя


05/06/08
477
mihaild в сообщении #1570066 писал(а):
Где-то в отладчике есть функция "отслеживать значение переменной" и останавливаться в месте, где значение меняется.

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

-- Вт ноя 15, 2022 19:08:56 --

TheRuinedMap в сообщении #1570081 писал(а):

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

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



Основную идею ваши утверждений я давно уловил. И с объектом типа bool это может для меня теперь иметь логическое объяснение, так как вероятность ощибки 50/50.
А вот другой вопрос, как к программисту (себя, к сожалению, не могу отнести к прфессиональным программистам), связанный с моим последним багом.
В цикле
Код:
for(int st =0; st < N; st++){
funk1(st,....);
funk2(st, ...);
[b]funkBag(st,...);[/b]
}

Проблемы с объектом int st;
Но исключительно в функции funkBag(st,...);;

Как по вашей интуиции, что разрушает эту переменную, процесс внутри этой функции, или снаружи?
Да и разрущение какое-то странное, такое впечатление, что значение параметра просто делится на 2.
Очень хотелось бы верить, что разрушение этого объекта не связано с удалением ненужных объектов типа BigInteger.
Иначе мне проблемы не решить.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 19:00 
Аватара пользователя


05/06/08
477
PS Свой неуд воспринимаю с пониманием.
Спасибо за участие.
Оба бага ликвидировал.
Была ошибка в коде.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 19:14 
Заслуженный участник
Аватара пользователя


16/07/14
9146
Цюрих
Используется синтаксис C++
int f(int x) {
    int r = 0;
    int z[1];
    if (z[x] == 0) {
        r += x;
    }
    if (z[x] != 0) {
        r += x;
    }
    return r;
}

Вот в таком коде текущая версия clang радостно оптимизирует f до return 0; https://godbolt.org/z/T4cKoaPW8. Хотя казалось бы в z[x] должен быть или ноль, или не ноль.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 20:47 


10/03/16
4444
Aeroport
mihaild в сообщении #1570102 писал(а):
Вот в таком коде текущая версия clang радостно оптимизирует f до return 0


То есть она типа понимает, что в массиве z мусор, поэтому нада воздержаться от исполнения условных операторов с его участием? :shock: :shock:

P.S. Почему компилятор не выдает ошибку? В чем ВООБЩЕ смысл использования неинициализированных переменных?

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 21:09 
Экс-модератор
Аватара пользователя


23/12/05
12063
MGM в сообщении #1570100 писал(а):
Была ошибка в коде.
Неудивительно, что ее не могли обнаружить другие, учитывая то, как вы приводили свой код, типа
MGM в сообщении #1570085 писал(а):
Код:
for(int st =0; st < N; st++){
funk1(st,....);
funk2(st, ...);
[b]funkBag(st,...);[/b]
}

На будущее:
1) Внятной помощи вы добъетесь гораздо быстрее, если будете приводить минимальный воспроизводящий проблему код, в том виде, в котором вы его компилируете, - Ctrl+C - Ctrl+V, без многоточий и прочих коррекций.
2) Будет удобнее, если для оформления больших фрагментов кода, использовать тег syntax, а не code.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 21:17 
Заслуженный участник
Аватара пользователя


16/07/14
9146
Цюрих
ozheredov в сообщении #1570113 писал(а):
То есть она типа понимает, что в массиве z мусор, поэтому нада воздержаться от исполнения условных операторов с его участием?
Скорее всего нет, потому что если увеличить размер массива, то эффект пропадает. Я предполагал, что компилятор понимает, что раз z[x] не выходит за границы, то x == 0, но замена обращения на z[x + 1] или присваиваний на r += x + 1; ничего не меняет, так что, видимо, дело не в этом.
ozheredov в сообщении #1570113 писал(а):
Почему компилятор не выдает ошибку?
В данном случае он выдает предупреждение.
ozheredov в сообщении #1570113 писал(а):
В чем ВООБЩЕ смысл использования неинициализированных переменных?
Ни в чем, их использовать (точнее читать из них) нельзя. Но надежно установить, точно ли в данном месте данная переменная инициализирована - тоже нельзя.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение15.11.2022, 23:59 
Аватара пользователя


28/10/21
100
ozheredov в сообщении #1570113 писал(а):
P.S. Почему компилятор не выдает ошибку?


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

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

ozheredov в сообщении #1570113 писал(а):
В чем ВООБЩЕ смысл использования неинициализированных переменных?


Это давняя тема, которая прошла целый ряд этапов в процессе своего развития. Когда-то использование неинициализированных переменных однозначно считалось неопределенным поведением. Но затем в силу ряда причин такая строгость была признана избыточной и слишком ограничивающей в рамках языков, в которых приходится периодически работать с "сырой" памятью или сталкиваться с padding-байтами в пределах агрегатных типов.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение16.11.2022, 03:53 
Заслуженный участник


02/08/11
7003
Не знаю что значит "строгость была признана избыточной". Даже в современном языке Rust неинициализированную память читать нельзя.

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение16.11.2022, 04:37 
Аватара пользователя


28/10/21
100
warlock66613 в сообщении #1570148 писал(а):
Не знаю что значит "строгость была признана избыточной". Даже в современном языке Rust неинициализированную память читать нельзя.


В С и С++ вам неминуемо приходится сталкиваться с неинициализированной памятью. Например, байты-заполнители (padding bytes), добавленные компилятором в struct-тип между его полями для обеспечения правильного выравнивания полей в общем случае являются неинициализированной памятью. И вы можете видеть и читать эти байты путем рассмотрения struct-объекта как массива байтов unsigned char[] или при копировании таких объектов через memcpy. Память, выделенная malloc тоже является неинициализированной и ограничивать работу с этой памятью правилами "читать можно только те байты, который ранее были записаны" - это тоже чересчур ограничивающее правило для эффективной низкоуровневой работы с памятью.

В С и С++ разделяют понятия
1) самого факта чтения неинициализированной переменной, и
2) использования прочитанного значения в дальнейшем коде

* В "классическом" С (первый стандарт С89/90) все бело четко: уже сам факт чтения неинициализированной переменной "мгновенно" приводил к неопределенному поведению. И до свидания. То есть сама попытка чтения любой неинициализированной переменной могла привести к пресловутому неожиданному "форматированию жесткого диска".

* В С99 это сочли слишком строгим и перекроили спецификацию так: чтение неинициализированной переменной дает либо неспецифицированное значение, либо т.наз. trap representation. В последнем случае, то есть при попытке чтения trap representation, сразу возникает неопределенное поведение. В первом случае неопределенного поведения не возникает, т.е. вы просто тихо получаете на руки какое-то неспецифицированное значение.

Ключевой момент тут заключается в том, что некоторые типы могут вообще не иметь trap representations. Например, unsigned char гарантированно не имеет trap representations. То есть в рамках таких правил неинициализированную переменную типа unsigned char можно спокойно читать - это не приводит к неопределенному поведению. Разумеется, само полученное значение не специфицировано, т.е. непредсказуемо. В результате при дальнейшей работе с этим значением вы получите неспецифицированное (но не неопределенное!) поведение. То есть предсказать, как будут работать ваши if-ы на неинициализированном unsigned char нельзя, но и внезапного "форматирования жесткого диска" произойти уже не может.

Например, кот такая функция в C99 не порождает неопределеного поведения и заведомо возвращает 0

Код:
// C99
int foo(void)
{
  unsigned char a; // Неинициализированная переменная, не имеющая trap representations
  a *= 2; // Неспецифицированное, но заведомо четное значение
  return a % 2; // Заведомо 0
}


Также было специально оговорено, что объекты struct-типов, рассматриваемые целиком, никогда не имеют trap representations. Что касается фундаментальных типов, то какие типы в вашей реализации имеют trap representations, а какие нет - зависит от реализации.

* В С11 решили, что и эта спецификация тоже не совсем адекватна: она не очень хорошо согласуется с теговыми регистрами на архитектурах вроде Itanium. На этой архитектуре всегда имело смысл помечать регистры процессора, содержащие неинициализированные значения, тегом NaT ("Not A Thing"), что приводило к исключению при попытке чтения такого неинициализированного значения. Это было весьма полезной фичей. А теперь С99 заявил, что для некоторых типов неинициализированные значения все таки "можно" читать, что в широком ряде случаев заставляет реализации насильно сбрасывать тег NaT для неинициализированных регистров, тратя на это процессорные циклы и принижая защитную ценность этой фичи.

Тогда вспомнили, что в С99 разрешение на чтение неинициализированных переменных вводилось в язык именно ради того, чтобы разрешить доступ к неинициализированный памяти, а на неинициализированные регистры процессора распространять эти разрешения смысла нет. И спецификацию подправили следующим образом: если ваша переменная является автоматической и используется в коде так, что ее теоретически можно объявить с классом памяти register (т.е. к ней некогда не применяется оператор взятия адреса &), то на нее распространяются "классические" строгие требования С89/90, т.е. любое чтение такой неинициализированной переменной - сразу неопределенное поведение. В противном случае для нее работают "расслабленные" требования С99 (http://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p2). Это последнее исправление известно под неформальным именем "Itanium clause".

Например:

Код:
// C11
void foo(void)
{
  unsigned char a, b; // Неинициализированные переменные, не имеющие trap representations
  &b;
  a += a; // <- Неопределенное поведение
  b += b; // <- Здесь нет неопределенного поведения
}


В таком виде нынешняя спецификация выглядит странновато, но, как видите, на то есть причины.

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

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение16.11.2022, 17:06 


10/03/16
4444
Aeroport
mihaild
Thanks, очень любопытно

-- 16.11.2022, 17:08 --

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


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

 Профиль  
                  
 
 Re: true(204) в C++ что за зверь?
Сообщение16.11.2022, 17:42 
Аватара пользователя


28/10/21
100
ozheredov в сообщении #1570208 писал(а):
TheRuinedMap в сообщении #1570139 писал(а):
в общем случае компилятор не может знать, вызывается ли такая функция где либо в коде.


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


Нет, конечно. Компилятор как таковой в С и С++ никогда не "компилит весь код", не видит "весь код" и ничего не знает о "всем коде".

С и С++ классически построены на принципе раздельной/независимой трансляции. Каждая единица трансляции (исходный файл) компилируется компилятором независимо и полностью изолированно от других единиц трансляции. То есть компилятор может обладать исчерпывающим знанием об использовании сущностей (переменных и функций) с внутренним связыванием внутри каждой отдельно взятой единицы трансляции, но как только речь заходит о сущностях с внешним связыванием - компилятор ничего не может знать о том, используются они в программе или нет, как они используются, где они используются и т.п. Эта информация компилятору как таковому недоступна в принципе.

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

---

Достаточно хорошо известный пример, который опирается именно на манеру оптимизатора в компиляторе Clang делать далеко идущие выводы на основе принципа "неопределенное поведение никогда не происходит" и анализа сущностей с внутренним связыванием - это

Код:
#include <stdlib.h>

static int (*Do)(void);

static int EraseAll(void) {
  /* return system("rm -rf /"); */
  return system("echo Hello World");
}

void NeverCalled(void) {
  Do = EraseAll; 
}

int main() {
  return Do();
}


http://coliru.stacked-crooked.com/a/75390398a17d0db7

Несмотря на то, что данный пример как будто выполняет вызов функции через нулевой указатель, в результате таки потенциально применяется пресловутый Патч Бармина :)

Однако достаточно убрать static с Do или добавить static на NeverCalled - и эффект пропадает. Это как раз таки иллюстрация того, что может и что не может знать компилятор о глобальных свойствах программы.

-- 16.11.2022, 07:17 --

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

Код:
#include <stdio.h>

int main()
{
  int x;   
  printf("%p - %d\n", (void*)&x, x);
  printf("%p - %d\n", (void*)&x, x);
}


Код:
0x7fff32a53d4c - 849690184
0x7fff32a53d4c - 0


http://coliru.stacked-crooked.com/a/b3c2c79ed2e34887

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 48 ]  На страницу Пред.  1, 2, 3, 4  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group