Страницы

Поиск по вопросам

понедельник, 8 октября 2018 г.

понять где undefined behavour в арифметических выражениях

довольно таки часто обсуждаемая тема, но тем не меее хотелось бы конкретнее разобраться где есть UB а где нет
Ниже несколько примеров, и мои мысли по поводу что есть что:
int i = 0, x = 1; int a[6] = {0, 0, 0, 0, 0, 0};
i = ++i + ++i; // UB i = i++ + ++i; // UB x = i++ + ++i; // ?? я думаю что UB x = i++ + i++; // OK a[i] += i++; // OK ?? a[++i] = i++; // UB ?? a[++i] = ++i // UB i += ++i; // UB j += j; // OK
За всё время я уяснил одно правило - если в одном выражении значение объекта меняется более одного раза - UB чистой воды,
тоесть, понятно, что на каких-то платформах может и будет происходит то, что ожидается, но вопрос в том, что стандарт в таких случаях ничего не гарантирует - тоесть либо говорится что это undefined behavour, unspecified behavour либо implementation defined behavour.
Вопрос - могут ли возникнуть в этих примерах случаи кроме UB ?


Ответ

Языки С и С++ фундаментально отличаются в одной важной детали: язык С++ старается максимально тщательно сохранять lvalue-ность результата выражения (lvalue-preserving language), а язык С наоборот - в большинстве случаев сразу беззаботно теряет lvalue-ность результата выражения (lvalue-discarding language)
int a = 0, b = 1; a = b; // lvalue в C++, rvalue в С ++b; // lvalue в C++, rvalue в С 1 ? a : b; // lvalue в C++, rvalue в С (a, b); // lvalue в C++, rvalue в С
Эти свойства данных языков диктуют серьезные различия в их подходу к упорядочению (sequencing) операций в выражениях. Первый стандарт С++ (С++98) пытался игнорировать этот момент и придерживаться унаследованного напрямую из С подхода к упорядочению, но в конечном итоге эта модель была признана дефектной и существенно переработана. В процессе этой переработки в С++ появилась упорядоченность, которой ранее не было. Как следствие, некоторые выражения, которые формально порождали UB в C++98, получили вполне определенное поведение в C++11. А C++17 добавил еще больше отношений упорядочения в язык С++, тем самым еще более расширив круг выражений, поведение которых определено.
Поэтому ответ на вопрос о наличии UB в выражении может существенно отличаться между С и С++. Например, выражение
i = ++i;
порождает UB в С, но имеет совершенно определенное поведение в С++. Различие возникает по той причине, что в языке С++ гарантируется, что модификация переменной i под действием преинкремента произойдет до того, как будет завершено вычисление результата преинкремента. В языке С ничего подобного не гарантируется.
Правила "если в одном выражении значение объекта меняется более одного раза" не существовало никогда и нигде. Более-менее правильной формой этого правила будет "если между парой соседних точек следования значение объекта модифицируется более одного раза, то поведение не определено". Также не следует забывать вторую часть этого правила: "если между парой соседних точек следования значение объекта модифицируется, а также присутствует независимое чтение значения этого объекта, то поведение не определено". После переработки в С++11 эти правила, однако, стали применимы только к языку С, но не к языку С++ (см. пример выше).
Также стоит заметить, эти правила основаны на концепции точки следования, в то время как современные спецификации этих языков (как С++, так и С) решили отказаться от этой концепции и заменить ее концепцией упорядочения (sequencing, sequenced before, sequenced after). Но, еще раз, в языке С эти правила по-прежнему достаточно точно отражают ситуацию с UB в выражениях.
Новое же правило, применимое как в С, так и в С++, звучит так
Если побочный эффект, воздействующий на скалярный объект, неупорядочен (unsequenced) по отношению к другому побочному эффекту, воздействующему на этот же скалярный объект, или по отношению к вычислению значения этого же скалярного объекта, то поведение не определено.
Различия же между С и С++ сводятся к отличающимся гарантиям того, что является упорядоченным (sequenced), а что нет (unsequenced). Упорядоченность оговаривается в описаниях конкретных операторов языка.
В ваших примерах
i = ++i + ++i; // UB и в С, и в С++ i = i++ + ++i; // UB и в С, и в С++ x = i++ + ++i; // UB и в С, и в С++ x = i++ + i++; // UB и в С, и в С++ a[i] += i++; // UB и в С, и в С++11, все в порядке в С++17 a[++i] = i++; // UB и в С, и в С++11, все в порядке в С++17 a[++i] = ++i // UB и в С, и в С++11, все в порядке в С++17 i += ++i; // UB в С, все в порядке в С++11 (?), все в порядке в С++17 j += j; // OK
(Я не уверен в своей трактовке i += ++i. A += B определено через A = A + B, но i = i + ++i - это UB и в С++. Но подозреваю, что ситуацию спасает то, что A в A += B вычисляется только один раз.)

Какая разница в typename и class в параметрах шаблона?

Я не совсем понимаю разницу между:
template
и
template
Если она есть, то в чём заключается?


Ответ

Стандарт C++ говорит следующее [п.14.1.2]:
There is no semantic difference between class and typename in a template-parameter.

Книги для программистов [закрыт]

Есть какие-нибудь книги которые должен прочитать каждый хороший программист?


Ответ

Любые книги, где описывается следующие: Структуры данных: Массивы и строки Связанные списки Стек и очередь Деревья и графы Алгоритмы и «концепции»: Сортировка и поиск Рекурсия Манипуляция битами Объектно-ориентированное проектирование Язык программирования Операционные системы Дизайн и юзабилити(опционально)

Как работает web приложение java?

Кто-нибудь может объяснить как работает java web приложение с аннотациями и spring-mvc совместно с сервером приложений? (В моем случае это Glassfish 4)
Когда мы работаем с консольным приложением java, мы указываем команде java имя скомилированного класса. Я так понимаю, JRE ищет в указанном классе метод public static void main(String args[]) и выполняет его. И этот метод main считается точкой входа в программу.
А когда у нас web приложение, у нас имеется какая-то точка входа? Я по началу думал, что точка входа это web.xml. Но если мы используем spring-mvc и конфигурируем его с помощью аннотаций, то я так понял web.xml вообще может быть пустым. Когда глассфишу скармливается war'ник, то я так понимаю в памяти создаются экземпляры классов которые помечены аннотациями @Service и @Controller, но кто их создает? Я читал, что это делает спринг, в рамках его IoC, DI и его контейнерной роли, но спринг тоже кто-то должен запустить? По логике, инициализация спринга должна происходить при явном или неявном вызове его конструкторов в точке входа, но где тогда она? Или его инициализацию производит сервер приложений? Но каким образом мы указываем ему это сделать?
Вот мой SpringInitializer, я так понимаю основная магия происходит здесь? Этот код выполняет Glassfish? Как он понимает, что именно этот класс надо выполнить?
import javax.servlet.MultipartConfigElement; import javax.servlet.ServletRegistration; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpringInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override protected Class[] getRootConfigClasses() { return new Class[] { SpringConfiguration.class }; }
@Override protected Class[] getServletConfigClasses() { return null; }
@Override protected String[] getServletMappings() { return new String[] { "/" }; } }
Я рылся в руководстве Шилдта по Java, а также в Spring in Action, и пару недель пробую различные запросы по сабжу в гугл. Или в этих книгах не было явных ответов на эти вопросы, или я их пропустил, или не понял.
Буду признателен за объяснение, или если кто тыкнет носом где читать.


Ответ

Я рылся в руководстве Шилдта по Java, а также в Spring in Action
Если вы хотите разобраться с базовыми механизмами работы веб-приложений начинать стоит со спецификации Servlet API (3.0, 3.1).
А когда у нас web приложение, у нас имеется какая-то точка входа?
Как таковой точки входа нет. Есть контейнер сервлетов, который берет ваш WAR и, если все хорошо, стартует жизненный цикл вашего веб-приложения: устанавливает параметры, оповещает слушателей, пробрасывает запросы в сервлеты, пропускает их через фильтры. По сути, контейнер сервлетов занимается тем, что оперирует объектами, которые можно описать в web.xml, а, начиная с 3й версии спецификации, он должен уметь искать их и без web.xml.
... но кто их создает? Я читал, что это делает спринг, в рамках его IoC, DI и его контейнерной роли, но спринг тоже кто-то должен запустить?
Все верно, IoC-контейнер спринга (а именно AnnotationConfigWebApplicationContext в вашем случае) сканирует видимый ему CLASSPATH и создает ваши бины, ориентируясь на аннотации. Осталось понять, кто создает экземпляр этого ApplicationContext и запускает его жизненный цикл (см. ниже).
По логике, инициализация спринга должна происходить при явном или неявном вызове его конструкторов в точке входа, но где тогда она? Или его инициализацию производит сервер приложений? Но каким образом мы указываем ему это сделать?
Вы очень верно рассуждаете. Начиная со спецификации Servlet API 3.0 появилась возможность не использовать web.xml. Но контейнер сервлетов должен как-то узнать, где находится код, инициализирующий веб-приложение. Тут помогает механизм, известный как Service Provider. Если коротко, он ищет в CLASSPATH для контейнера сервлетов файл-дескриптор META-INF/services/javax.servlet.ServletContainerInitializer, который указывает на реализацию интерфейса ServletContainerInitializer. В случае Spring-а это будет класс SpringServletContainerInitializer.
Давайте без стеснения посмотрим на его исходники. На объявлении класса висит аннотация @HandlesTypes
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer {}
По контракту интерфейса ServletContainerInitializer контейнер обязуется передать конкретной реализации в её единственный метод onStartup коллекцию классов, попадающих в перечисленные в аннотации @HandlesTypes. Здесь у нас указан только интерфейс WebApplicationInitializer, поэтому Glassfish передаст все найденные реализации WebApplicationInitializer. Для каждой реализации будет создан экземпляр и вызван метод onStartup
for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }
И именно ваш SpringInitializer является такой реализацией. Кроме того SpringInitializer наследует AbstractDispatcherServletInitializer. И вот именно этот AbstractDispatcherServletInitializer и "запускает Spring", создавая WebApplicationContext. Все сошлось.

PS. Читайте спецификации и ознакомьтесь с JavaDoc упомянутых в моем ответе классов. В Spring вообще любят подробно описывать все "магические" механизмы в документации ключевых абстрактных классов и интерфейсов.

Integer которые передается в static метод, передается как ссылочный Object?

Я думал я Java знаю хорошо. Имеем int - примитивный тип. Когда передаем его в метод, то в методе не ссылка на переменную, а лишь копия. Я полагал что Integer, полноценный класс упаковки int передается в метод по ссылке и любые манипуляции с переменной Integer переданные в метод будут происходить непосредственно с той переменной которую мы передали, а не какой то копией.
Я не понимаю, почему следующий код работает против моего понимания типов в Java.
Integer a=5; inc(a); System.out.println(a);
private static void inc(Integer a){ a++; }
Output:
5
Объясните, почему 5, а не 6???


Ответ

Вы все правильно сказали, все аргументы при вызове метода в java передаются по значению, но тут один нюанс. java.lang.Integer является неизменяемым типом, и когда происходит инкрементирование, то выполняется unboxing, увеличение значения и снова упаковка в объект, т.е. создается уже совсем другой объект.

Исключить повторяющиеся значения из Dictionary

Есть Dictionary, к примеру
IDictionary>
есть модель
public class Model { public string Name { get; set;} public byte[] Data { get; set;} }
Как можно исключить повторяющиеся значения для Name во всем Dictionary? Отфильтровать существующий словарь. Повторяющиеся значения Value необходимо удалять. Т.е. если в ключе "Key1" и в ключе "Key2" в списке есть Name с одинаковым значением, то необходимо исключить это значение для ключа (не важно какого)
тестовые данные
{"key1", {"Name1", }, {"Name2", }, {"Name3", }} {"key2", {"Name4", }, {"Name2", }, {"Name5", }}
результат
{"key1", {"Name1", }, {"Name2", }, {"Name3", }} {"key2", {"Name4", }, {"Name5", }}
или
{"key1", {"Name1", }, {"Name3", }} {"key2", {"Name4", }, {"Name2", }, {"Name5", }}


Ответ

HashSet uniqueNames = new HashSet(); foreach (KeyValuePair> pair in dict) { foreach (Model model in pair.Value.ToList()) { if (!uniqueNames.Add(model.Name)) { pair.Value.Remove(model); } } }

Зачем использовать uint32_t?

Гарантируется ли, что типы с явным указанием размера (такие, как uint32_t, uint64_t, uint16_t) занимают одинаковое количество памяти на всех платформах (на десктопах, на мобилах, на микроконтроллерах)? Собственно, независимо от ответа, как эти типы можно использовать, есть ли какие-нибудь особенные задачи, где очень нужно знать размер в битах? Маски, флаги? Верно ли то, что в современном C обычный деревянный int под неким табу и не стоит использовать типы, размер которых меняется от платформы к платформе (здесь важны ссылки на стандарт, если он каким-то образом это регулирует или на мануалы к компилятору)?


Ответ

типы с явным указанием размера
Стандарт гарантирует их размеры, но не их существование. Т. е. теоретически можно встретить платформу, на которой нет нужного типа. Зато если уж программа скомпилировалась, то размер типа ты точно знаешь.
Там ещё есть типы со словом least, например, uint_least32_t, если тебе надо хотя бы 32 бита.
обычный деревянный int
Стандарт не гарантирует его размер.
не стоит использовать типы, размер которых меняется от платформы к платформе
Всё зависит от задач. Если мне нужна 32-битная маска для перебора, то логично не полагаться на int. Если какой-то размер, то есть size_t, зависящий от битности программы. А если мне нужно вывести число процентов, то можно и обычный int взять.