Страницы

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

Показаны сообщения с ярлыком null. Показать все сообщения
Показаны сообщения с ярлыком null. Показать все сообщения

суббота, 15 февраля 2020 г.

Java типы, допускающие NULL значения

#java #c_sharp #null


Существуют ли в Java что-то вроде типов, допускающих NULL значения? То есть с сервера
приходит, на пример, int, но вместо него может прийти NULL В C#можно это обработать,
используя типы, допускающие, NULL значения. 

int? a = NULL;


Как это следует обрабатывать в Java?
    


Ответы

Ответ 1



В Java ссылка любого класса может принимать значение null. Примитивы же... Целые числа - byte, short, char, int, long Числа с плавающей точкой (иначе дробные) - float, double Логический - boolean ...лишены такой возможности. Если требуется создать ссылку на один из примитивных типов данных, необходимо использовать соответствующий класс-обертку. Также в таких классах есть некоторые полезные методы и константы, например минимальное значение типа int можно узнать использовав константу Integer.MIN_VALUE. Оборачивание примитива в объект называется упаковкой (boxing), а обратный процесс распаковкой (unboxing). (источник) Соответственно для решения вашей задачи необходимо использовать классы-обёртки примитивов: int -> Integer, short -> Short и т.п.

Ответ 2



В java для этого используются классы обертки: wrapper for int: Integer wrapper for double: Double Но будьте внимательно, типы вроде Integer передаются по ссылке, а не по значению.

пятница, 14 февраля 2020 г.

Ошибка NoneType is not iterable в цикле

#python #list #null


В цикле выводит ошибку:


  TypeError: argument of type 'NoneType' is not iterable


Пытался исправить так:

if None in listUsers:
    listUsers.remove(None)
for user in listUsers:
    adj[user] = getFriends(user)


Ошибка остаётся прежней, но уже в строке с условием. Как это можно исправить?
    


Ответы

Ответ 1



Попробуйте заменить весь код из вопроса на следующий: adj = {user:getFriends(user) for user in listUsers if user} if listUsers else {}

Ответ 2



Вероятно у вас в прежнем коде случилось, что listUsers is None. Чтобы проверить это, напишите на консоли Питон: listUsers = None None in listUsers и на выводе вы получите TypeError: argument of type 'NoneType' is not iterable Значит, что вы нужны или применить тест перед вашим кодом (if listUsers is not None:) или использовать другой подход (например как в соседнем ответе).

суббота, 8 февраля 2020 г.

Текстовые строки и NULL

#строки #null #любой_язык


Нынче вечер пятницы, поэтому мой вопрос не практического, а сугубо философского свойства,
для потрепаться. Каких-то материалов наверняка можно нагуглить, но гуглить неохота,
хочется поговорить.
Есть такая концепция — NULL-значения. Смысл: значение отсутствует, неизвестно, не
имеет смысла и т.п.
Если речь идёт, например, о дате рождения, то заданная дата — это дата рождения,
а NULL означает, что даты рождения гражданина мы по той или иной причине не знаем.
Ну или что он ещё не родился, сидит у мамы в животе.
При этом числовой 0 и NULL несут разную смысловую нагрузку. Если речь идёт, например,
о сумме денег на банковских счетах гражданина, то 0 может означать, что денег у него
нет, а NULL — что мы не знаем, сколько у него денег (не посчитали пока).
Во многих случаях NULL неприменим. Например, в банковском софте в записи о банковском
счёте сумма всегда известна точно — какой же это банк, если он сам не знает, сколько
на счёте денег?
Сказанное выше давно известно, это была прелюдия к вопросу. А вопрос такой:
Если у нас текстовое поле, то в каких случаях пустая строка и NULL несут разную смысловую
нагрузку?
Я думал и не смог придумать ни одного практически применимого примера. Складывается
впечатление, что пустой текст и NULL во всех случаях должны рассматриваться как эквиваленты.
Если позволить себе немного пофилософствовать, то я считаю, причина в том, что текст
— это очень особенный тип, в максимально общем виде выражающий древнюю, докомпьютерную
идею письменности. Вот у нас чистая восковая дощечка, на ней ни хрена нет, пусто. А
потом мы на ней чего-то накарябали стилом и теперь у нас есть текст. Легко заметить,
что «пустой текст» и «отсутствие текста» не различаются никак. Текст выпадает из состояния
«отсутствует» только тогда, когда в нём появляется хотя бы один символ.
Здесь уместно возразить, что NULL может обозначать отсутствие самой восковой дощечки,
однако, возвращаясь к информационным технологиям XXI века, хочется спросить: есть ли
примеры, где это нужно?
А что вы думаете про эквивалентность NULL-а и пустой строки? В каких случаях пустая
строка и NULL несут разную смысловую нагрузку?
Интересны любые примеры и соображения.
P.S. Речь, понятно, не только про классические SQL-СУБД, но про систему типов и значений
в целом, где бы она ни применялась.    


Ответы

Ответ 1



Да в куче случаев пустая строка и NULL могут иметь разный смысл. Например, текст инструкции. Если его не загрузили (еще не поместили в систему) -- NULL, а если просто сам текст отсутствует -- пустая строка. И позицию Oracle тоже понять можно. Все же SQL концептуально декларативный, а не процедурный язык, поэтому эквивалентность пустой строки и NULL может здорово облегчить жизнь разработчиков.

Ответ 2



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

Ответ 3



Очень простой пример из жизни баз данных. База данных какой-нибудь больницы. Поле "отчество" допускает состояние пустоты >null<. Мы можем ничего не знать об отчестве от пациента (если он без сознания и без документов), тогда это >null<. И пациент по документам может не иметь отчества - ну, пусто у него в графе "отец". Или он принадлежит народности, где отчеств не бывает. Тогда в базу пишется не >null<, а пустая строка - мы знаем, что отчество есть, и знаем, что оно - пустое.

Ответ 4



NULL очень удобен именно как признак того, что у переменной вообще нет никакого значения, даже пустого. Например, для организации мемоизации. #include int main(void) { char* txt = NULL; for (int i = 0; i < 10; i++) { if (txt == NULL) { txt = "Hello World!"; } puts(txt); } return 0; }

Ответ 5



А в чём филосовское различие NULL(SQL) и null(нулевой указатель)? Судя по статье, в том, что операции сравнения по-разному определены. То есть NULL == NULL; // возвращает NULL или UNKNOWN null == null; // возвращает `true` Отсюда и филосовская разница для строк. Две пустые строки - одно и то же. Два значения NULL - разные вещи. Если надо проверить, что две строки (из внешних данных) равны, лучше использовать концепцию пустых строк, а не NULL.

Ответ 6



Когда из БД тянем несколько десятков параметров и объединяем их в одну строку, гораздо проще заранее сохранять их там (в случае отсутствия данных) в виде пустой строки (""), чем потом, перед объединением, каждый параметр проверять на то, что он не NULL. Dim fio As String = "Муслиев" & "Колабельды" & "" Меньше возни с исключениями...

пятница, 24 января 2020 г.

Kotlin. Перегрузка методов под тип Null

#kotlin #интерфейс #null


Добрый день.
Пытаюсь реализовать интерфейс примерно такого вида:

interface IDataConversions {
    fun setData(value: Boolean?)
    fun setData(value: Byte?)
    fun setData(value: Short?)
    fun setData(value: Int?)
    fun setData(value: Float?)
    fun setData(value: ByteArray?)
    fun setData(value: String?)
}


Однако заметил, что в таком случае придется в каждом методе проверять на Null

Есть возможность в Kotlin реализовать что-то типа такого? : 

interface IDataConversions {
    fun setData(value: "какой то Null тип")
    fun setData(value: Boolean)
    fun setData(value: Byte)
    fun setData(value: Short)
    fun setData(value: Int)
    fun setData(value: Float)
    fun setData(value: ByteArray)
    fun setData(value: String)
}

    


Ответы

Ответ 1



Также, как и в джаве, при перегрузке будет браться сначала более конкретный тип. Выглядит так, что вам просто нужно перегрузить метод с параметром Any?: interface IDataConversions { fun setData(value: Any?) fun setData(value: Boolean) fun setData(value: Byte) ... }

вторник, 31 декабря 2019 г.

nil в языках программирования

#проектирование #любой_язык #null #типизация


Меня интересует как используется nil в различных языках программирования, и вообще
насколько это удобно с точки зрения дизайна языка.
В данной теме обсуждаются исключительно динамически типизированные языки.
К примеру в JavaScript есть два значения похожих на nil: null и undefined. (Нужно
ли было плодить?)
В Scheme есть #f, но нет nil. В Factor тоже нету nil, и используют f. (Нужен ли nil
если есть false?)
Кроме того, как-то читал что вроде в первых версиях Objective-C был объект nil (Objective-C
не знаю вообще), который поглощал вызовы, был чёрной дырой. Т.е. вёл себя как NaN для
чисел. На любое сообщение объект возвращал nil.
Насколько допустимо трактовать "пустые" объекты (пустой список, пустой словарь и
т.д.) как false, и есть ли у такой фичи опасные побочные эффекты, или вообще расположение
к каким-либо багам.
Приветствуются:

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


Ответы

Ответ 1



NULL нужен там, где есть ссылки. Почему в JavaScript есть еще undefined? Потому что null и undefined - разные объекты. По умолчанию ссылка указывает на undefined, а null - элемент логики. Сейчас ссылка null, потом не null. Скажем так, в С/С++ указатели тоже не null изначально, а undefined. Пустые объекты - не false, потому что false - семантическое значение. Элемент логических выражений, а null - нет (несмотря на то, что он используется в них, он преобразовывается). Если кратко, то наличие пустого объекта обязательно для языков со ссылками, а false - это отдельный объект, такой же как строка "ABC" или 4 - т.е. непустой, а семантически значимый.

Ответ 2



Хотя автор и задает вопрос по смыслу nil/null по отношению к динамическим языкам, но я считаю, что наиболее ярко смысл NULL раскрывается в SQL, а именно в случае со связанными запросами: пресловутые inner/left/right join). Без понятия NULL (то есть нет данных) весь SQL сильно теряет, если не сказать большего - вообще невозможно становится работать без NULL.

Ответ 3



Я считаю, что использование NULL важно в процессе отладки программы (скрипта). false - это все же нормальное значение переменной, а вот NULL - это именно отсутствие какого либо значения, вернее оно ПУСТОЕ. Обращение к NULL переменной может вызвать определенные исключительные ситуации, которые всегда вызовут ошибку работы программы, и именно это дает возможность легко найти и устранить эти ошибки. Я всегда использую именно NULL, как при возврате значений из функций, так и при передаче значений в функцию. Проверяю не просто !переменная, а именно == != NULL... На мой взгляд так красивее )) Удачи в поисках ответов!

как правильно сравнивать с null?

#java #null


как правильно писать 

if(obj ==null){}


или 

if(null == obj){}


в чем разница?

и вторая часть вопроса можно ли это заменить на 

static boolean  isNull(boolean val)


или нет? и что это такое?
    


Ответы

Ответ 1



Особой разницы нет. Но есть не особая. Вариант с if(null == obj) уберегает вас от распространенной ошибки типа if(obj = null) то есть случая, когда вместо сравнения по недосмотру выполняется присваивание. В случае с null = obj компилятор сообщит об ошибке, поскольку нельзя присваивать что-то null'у. Все вроде бы здорово, и отличие вроде бы и правда есть. Но это замечательное преимущество практически не имеет никакой ценности, поскольку будет работать только в том случае, если obj имеет тип Boolean. В остальных случаях компилятор тоже догадается сообщить об ошибке, поскольку результат такого присваивания не может быть приведен к булевскому типу. Поэтому в java разницы нет практически никакой. А вот в Си это может иметь смысл - там NULL - это всего лишь макрос, под которым обычно скрывается 0, и ошибки типа if(obj = NULL) довольно распространены, так как присваивание указателю NULL'а вполне допустимо, а случайно написать = вместо == - дело нехитрое. Может такая ошибка случиться и в JavaScript в силу того, что типизация в нем динамическая, и любую переменную можно неявно привести к булевскому значению. С другой стороны, в js чаще принято писать условия типа if(!obj) вместо if(obj == null) хотя так делают далеко не все. На мой взгляд конструкция типа null == obj выглядит коряво, но это дело вкуса.

воскресенье, 29 декабря 2019 г.

C/C++, strlen(NULL)

#c #null #strlen


Почему эта функция не проверяет переданный ей аргумент на NULL? 

Самое интересное, что мало где пишут, что strlen(NULL) приводит к сегфолту. Даже
в книжке С.Прата за 2015 год нет ни слова о том, что функция не выполняет названную
проверку. Очень странно.
    


Ответы

Ответ 1



На самом деле ничего странного в этом нет. Средства стандартных библиотек C и C++ часто не используют дополнительных проверок корректности аргументов в угоду эффективности. Многие средства уже предполагают, что программист сделал проверку, либо она вовсе не нужна, т.к. есть какая-то гарантия валидности аргумента. Если же сделать проверку внутри функций, то получим потерю производительности из-за лишних проверок. Возьмем простой пример: if (str) {//Проверка на ноль size = strlen(str);//А внутри strlen была бы еще одна проверка } else { printf("error\n"); } Тоже самое можно отнести, например, к std::vector::operator[]: for(size_type i = 0; i < vec.size(); ++i) { //Мы не выйдем за пределы вектора vec[i] = i;//Но в operator[] всё равно была бы еще одна проверка } И если с вектором всё более менее понятно - есть отдельная функция-член at, проверяющая валидности индекса, то с strlen чуть сложнее. Что должна вернуть эта функция, если ей передали NULL? Нужна была бы какая-то константа для определения факта ошибки и опять же, дополнительные проверки. К слову о том, что об этом нигде не пишут. На самом деле - пишут. Например, на cppreference: https://en.cppreference.com/w/cpp/string/byte/strlen Parameters str - pointer to the null-terminated byte string to be examined Аргументом strlen должен быть указатель на null-terminated byte string, коим NULL не является. Но можно обратиться и к стандарту C. Для strlen никаких требования не указано, поэтому согласно Вырезка из документа N1570 7.24.1 String function conventions ... Unless explicitly stated otherwise in the description of a particular function in this subclause, pointer arguments on such a call shall still have valid values, as described in 7.1.4. аргумент должен иметь валидное значение, согласно описанному в 7.1.4: 7.1.4 Use of library functions Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer, or a pointer to non-modifiable storage when the corresponding parameter is not const-qualified) or a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined а согласно этому пункту, как видим, передача нулевого указателя приводит к неопределенному поведению.

вторник, 24 декабря 2019 г.

Язык C, malloc() и NULL

#c #windows #null #malloc


Хотел бы обсудить, что делать, если malloc() вернул NULL.

Ясное дело, что если ведется разработка библиотеки или какого-либо "кирпичика", который
будет использоваться неизвестно в каких условиях, то вызов abort() совершенно неприемлем. 

Например, если пишется загрузчик ресурсов из файла в оперативную память, то лучше
бы сказать "извини, памяти нет", чем внезапно упасть неизвестно по какой причине.

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

В Windows 10/8/7/Em/CE тоже так?

2) Если память выделяется маленькими порциями, то может произойти такое, что при
неудачном malloc() мы хотим сделать printf(), не говоря уже про fopen() + fwrite(),
а памяти нет, и все совсем падает. Это реально?

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

Приложение многопоточное. Одни потоки отправляют/получают данные, другие устанавливают
соединение по TCP, третьи - ведут файлы журналов. Много библиотек, часто - закрытых в dll.

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

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

4) Я так понимаю, на исчерпывающем большинстве промышленных устройств сторожевой
таймер предусмотрен как раз из-за того, что существуют вот такие неразрешимые программным
путем проблемы, как неудачный malloc() и исчерпание памяти?
    


Ответы

Ответ 1



malloc всегда должет возвращать адрес, если malloc вернул null тогда это аварийная ситуация. Вы можете 1) Прекратить работу всей программы. 2) Вы можете прекратить работу "процесса" (не процесса как Process, а класса, библиотеки, функции или набора функций, например процесс компрессии данных), который не может завершить свою работу, освободить память занимаемую этим "процессом". Тут я солидарен с alexolut Надо не забывать что у программы есть детерминированое - определённое поведение, и недетерминированое. Нужно стремится делать детерминированое поведение. В программе желательно до начала процесса (а не вовремя) опеределить сколько нужно на процесс памяти, и не начинать процесс (или работу программы) если его не возможно выполнить. Нужно ограничивать аппетиты пользователя, если вам вместо 100Мб прислали 10Gb, то сообщить что файлы/потоки свыше 2Gb ваша программа не обрабатывает (условно, для примера). Нужно определить заранее на какие обьёмы вы хотите расщитывать, и проверять есть ли они (обьемы) в наличии. Если вы резервируете память столько незнаю сколько - то для небольших программ будет всё ок, а для больших - может закончится нехваткой памяти. Т.е. невозможно сделать malloc столько незнаю cколько. В ответственных за скорость местах лучше использовать пулы памяти. Недостаток пулов - часть "неиспользуемой" памяти просто лежит. Нужно соблюдать несколько правил... и тогда у вас память не закончится неожиданно. резервировать в ответственных местах память заранеее ограничивать глубину рекурсии в рекурсивных функциях (хвостовая рекурсия - отдельный случай) соблюдать порядок malloc и free. Желательно разделять память для долгого использования и для кратковременного. Сколько зарезервировано паралельными процессами. Проверять наличие утечки памяти (когда malloc больше чем free, но для этого нужно знать какое число, разница колличества вызовов malloc/free должна быть, и для этого нужно знать сколько памяти долгого использования зарезервировано). делать пулы, или массивы памяти, для возможности освободить память по завершению "процесса" которую будет использовать конкретный процесс (действие) (упростит освобожнение). память может закончится в двух случаях а) неправильно построеная "модель памяти" (неопределённое поведение программы которое не предусмотрено) б) пользователь использует компьютер с реально маленьким размером оперативки и файлом подкачки. Второй случай редкий, но проверить его на старте стоит. Ещё хочу заметить, что ОС уровня "приложение" оперирует виртуальной памятью, поэтому может такое быть, что ваше приложение занимает меньше памяти чем вы резервировали. Тут Язык C, глобальные массивы часный случай когда реально приложение занимает меньше памяти, чем виртуально. Если для вас важна стабильность - резервируйте при запуске нужное количество памяти - и проблемы с malloc у вас не будет. Ведь главный вопрос не в том хватает памяти или не хватает, а в том - сколько вам нужно.

Ответ 2



Если нужно подготовиться к обработке исчерпания памяти, то первым делом стоит обеспечить (зарезервировать) уже некоторый объём памяти для этих нужд. И всю обработку производить, опираясь на эти, заранее выделенные, объёмы. Когда память закончилась - это значит, что что-то пошло не так, что-то что мы заранее не учли. Ведь в нормальной ситуации устройство должно обычно обеспечивать режим работы 24/7. Поэтому когда память вдруг неожиданно кончилась, самое правильное, что здесь можно сделать, это залогировать проблемную ситуацию и перезапуститься (упомянутый вами сторожевой таймер как раз для этих целей). А вот для логирования как раз будет полезен заранее предопределённый объем память. Работать с ним можно по-разному: либо непосредственно ссылаться на эту память, либо выполнить для неё free и рассчитывать, что последующие malloc, необходимые для корректной обработки аномальной ситуации, отработают корректно, т.е. предоставят недавно освобождённую память.

Ответ 3



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

пятница, 20 декабря 2019 г.

Как сделать массив double nullable

#c_sharp #массивы #null


Как сделать массив double nullable?

double[,] numbersArray = new double[3, 3];

    


Ответы

Ответ 1



1 вариант: double?[,] numbersArray = new double?[3, 3]; 2 вариант: Nullable[,] numbersArray2 = new Nullable[3, 3]; можно даже так: Nullable[,] numbersArray3 = new double?[3, 3]; И вот так: double?[,] numbersArray4 = new Nullable[3, 3]; Выбирайте!

Ответ 2



Не удержусь от того, чтобы не добавить ещё один редко используемый метод: создание с одновременной инициализацией. Обратите внимание, new не используется. double?[,] numbersArray5 = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } }; и соответственно Nullable[,] numbersArray6 = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } };

четверг, 19 декабря 2019 г.

Значение null в java

#null #java #jframe


Скажите, что такое null в java? Пример:
// это только фрагмент
public main() {
    JFrame f = new JFrame();
    f.setLocationRelativeTo(null);
    // и так далее 
}
    


Ответы

Ответ 1



null - читается нал означает отсутствующее значение или ссылку null может быть сконвертирован (кастирован) в любой тип/класс/объект нельзя создать/декларировать тип с типом null

Ответ 2



Это значит отсутствие значения (не 0, а именно отсутствие).

Ответ 3



Первая ссылка в гугле по запросу "null в java". There is also a special null type, the type of the expression null, which has no name. Because the null type has no name, it is impossible to declare a variable of the null type or to cast to the null type. The null reference is the only possible value of an expression of null type. The null reference can always be cast to any reference type. In practice, the programmer can ignore the null type and just pretend that null is merely a special literal that can be of any reference type.

среда, 18 декабря 2019 г.

Может ли NULL давать не нулевое значение?

#c #null


При программировании часто использую подобное выражение:

if (pointer) {
    ....
}


что эквивалентно (по крайней мере для моей системы):

if (pointer != NULL)


где pointer указатель. Хочу удостовериться, что этот код не несет в себе опасности
на других системах и т.д. В учебнике нашел только, что NULL возвращает значение не
указывающее ни на какой объект. Но равно ли это значение 0 априори?
    


Ответы

Ответ 1



Также внесу некоторые дополнения к ответам, которые дали @alexolut и @VladD. Как вообще C решает истинно выражение expression в операторе if или нет: if ( expression ) statement if ( expression ) statement else statement ? Пункт 6.8.4.1 / 2 говорит следующее: In both forms, the first substatement is executed if the expression compares unequal to 0. In the else form, the second substatement is executed if the expression compares equal to 0. [...] То есть, выражение expression сравнивается с нулём. Если оно не равно нулю, то выражение expression считается истинным, если оно равно нулю, то expression считается ложным. Таким образом, примеры в вашем вопросе интерпретируются так: if ( pointer != 0 ) if ( (pointer != NULL) != 0 ) В стандарте языка явно оговорено (6.3.2.3 / 3), что и 0, и (void *)0, и NULL — нулевые указательные константы (null pointer constant). Для целей сравнения указателя и нулевой указательной константы, нулевая указательная константа преобразуется к типу указателя (6.5.9 / 5): [...] If one operand is a pointer and the other is a null pointer constant, the null pointer constant is converted to the type of the pointer. [...] Результат такого преобразования — нулевой указатель (null pointer). Если pointer не является нулевым указателем, то согласно пункту 6.5.9 / 6, оба выражения: pointer != 0 и pointer != NULL — истинны. Некоторого пояснения заслуживает выражение (pointer != NULL) != 0. Относительно операторов != и == стандарт языка говорит следующее (6.5.9 / 3): [...] Each of the operators yields 1 if the specified relation is true and 0 if it is false. The result has type int. [...] Таким образом, если pointer — не является нулевым указателем, то выражение (pointer != NULL) — равно 1, а значит выражение (pointer != NULL) != 0 истинно.

Ответ 2



В дополнение к ответу @alexolut отмечу, что битовое представление нулевого указателя не обязательно состоит из всех нулевых битов. Тем не менее, даже при таком представлении выражение (void*)0 должно давать нулевой указатель (то есть, приведение типов должно менять внутреннее представление), а также сравнение с нулём (if (p == 0)) и проверка if (p) должны работать ожидаемым образом.

Ответ 3



Из стандарта языка С11, 6.3.2.3/3: An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. Таким образом проверка на 0 эквивалентна проверке на NULL.

среда, 4 декабря 2019 г.

Язык программирования без null

#null #дизайн_языка #типизация


Энтони Хоар, человек который ввёл в употребление NULL-указатель высказал следующую мысль:

I call it my billion-dollar mistake. It was the invention of the null reference in
1965. At that time, I was designing the first comprehensive type system for references
in an object oriented language (ALGOL W). My goal was to ensure that all use of references
should be absolutely safe, with checking performed automatically by the compiler. But
I couldn't resist the temptation to put in a null reference, simply because it was
so easy to implement. This has led to innumerable errors, vulnerabilities, and system
crashes, which have probably caused a billion dollars of pain and damage in the last
forty years.

Простите, что не по-русски, не нашёл качественного перевода.
Суть в том, что он считает введение NULL ошибкой, которая стоила многих сил, и что
вместо введения NULL необходимо было ввести дополнительные проверки во время компиляции.
Мне интересно, возможна ли жизнь без NULL, и как это особенно согласуется с динамическими
языками (быть может для языков со статической типизацей этого и можно было избежать,
а для динамических -- нет?). Вообще, как много есть языков, которые обходятся без NULL
или эквивалента?
Как минимум один мне известен: Haskell.
UPD:


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

    


Ответы

Ответ 1



Проблема заключается не в самом null, его использование абсолютно легально, т.к. является по сути не артефактом конкретного языка программирования, а вычислительным приемом, паттерном, - общим для всей теории программирования. Всегда существуют вычислительные процессы (функции), работу которых можно оценивать с двух позиций: 1) есть результат; 2) нет результата. C этой точки зрения значение null есть унифицированный способ кодирования ситуации "нет результата". Проблема заключается в способе интеграции этого паттерна в систему типов языка. Значение null в большинстве языков не имеет типа, точнее, null является значением некоторого специального типа, являющегося подтипом ВСЕХ типов нашего языка. Поэтому null может быть числом, строкой, кнопкой пользовательского интерфейса, и вообще принимать любую форму. По сути дела, null - это "хак", непонятно как вписавшийся в статическую типизацию артефакт динамической типизации. Именно здесь начинаются проблемы, о которых пишет Хоар, и с которыми я согласен на 200%. Такой способ интеграции значения null означает, что в ЛЮБОМ месте, где мы ожидаем некоторое значение, мы можем получить null. И для нас как программистов нет способа гарантированно узнать, получим ли мы его или нет, а для компилятора гарантированно проверить, что в коде мы учли ту ситуацию, когда вместо ожидаемого результата получен null. Ситуацию могли бы несколько поправить (но не спасти!) хорошая документация и дисциплинированность программистов. Если бы не тот факт, что именно этих "добродетелей" в реальности почти не встретишь. Чтобы исправить проблемы null, нужно выполнить два условия: программисты должны быть лишены возможности использовать null в тех местах, где ЯВНО не объявили такую возможность; компилятор должен проверять и гарантировать нам, что клиентский код учел все такие ситуации, где вместо результата может быть получен null. Это можно сделать одним способом: сделать null значением некоторого обычного типа. В Haskell этим типом является Maybe, в Scala - Option, в F# - option. Значения null для этих языков называются соответственно Nothing, None и снова None. Тогда все встает на свои места: Выполнение 1-го условия: // Ошибка компиляции: мы не указали ЯВНО, что можно использовать None def notLessThan5(x: Int): Int = if (x >= 5) x else None // OK def notLessThan5(x: Int): Option[Int] = if (x >= 5) Some(x) else None Выполнение 2-го условия: // Ошибка компиляции: мы не учли, что notLessThan5() может не вернуть результата (вернуть None) println(notLessThan5(10) + notLessThan5(3)) // OK println(notLessThan5(10).getOrElse(5) + notLessThan5(3).getOrElse(5)) // напечатает 15 Во всех случаях соблюдение правильных принципов работы со значениями null проверит за нас компилятор, не допустив, чтобы NullPointerException "всплыла" в самый неподходящий момент во время эксплуатации программы. Проблемы null решены. Следует особенно заметить: мы не отказываемся от использования null. Мы просто интегрируем этот null в систему типов иначе, чем это сделано сейчас, в Java и им подобных. None, Nothing и проч. - есть точные эквиваленты null, только интегрированные в систему типов. Динамические языки программирования не используют преимущества статической типизации, поэтому все, что описано выше, их не касается. Избежать тех проблем, о которых говорил Хоар, в динамических языках нельзя в принципе - любая функция может вернуть ЛЮБОЕ значение. Это касается не только null, но вообще любых значений. Поэтому умные люди, которые используют динамические языки в повседневной практике, давно уже описали в литературе защитные практики от гибкости динамических языков. Основной практикой, помимо дисциплины и документации, является повсеместное и всеобъемлющее тестирование кода, причем как можно раньше, в идеале, до написания самого кода (TDD). Чудес не бывает: верификацию при проверке типов приходится заменять верификацией при тестировании. На эту тему рекомендую послушать великолепный доклад Robert Martin'а, произнесенный на RailsConf'09, "What Killed Smalltalk Could Kill Ruby, Too".

Ответ 2



Если поставить себе такую цель - написать программу не использующую NULL, скажем на C++, то это вполне осуществимо. Так что, в общем случае без NULL на C++ жить можно. На C# сложнее будет это сделать, поскольку большинство методов в .NET могут вернуть null. И как минимум проверять на null придется. Но это, согласитесь, уже не проблема языка, а проблема библиотек. Были бы библиотеки не использующие null, и программы можно было бы писать без него. К тому же, можно создать классы-обертки для того чтобы они инкапсулировали в себе работу с NULL и для внешнего наблюдателя никаких нулов не будет. Правильные программы на C++ пишутся именно так. Не думаю, что эти рассуждения не верны для других языков.

Ответ 3



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

суббота, 30 ноября 2019 г.

Сравнение типов значений с null

#c_sharp #null


Самый простой пример - метод Add() класса Dictionary. При добавлении ключа,
проверяется равен ли он null и для этого параметр key приводится к object на тот вариант,
если TKey значимый тип. Однако меня интересует целесообразность упаковки valueType,
ведь условие if(key == null){} будет работать исправно в любом случае.
Т. о. если я правильно понимаю, приведение к object нужно лишь для того чтобы не
получить предупреждение?
upd.
Скит пишет
int i = 5; 
if (i == null) 
{ 
   Console.WriteLine ("Never going to happen"); 
}


Компилятор С# выдаст для этого кода
предупреждение, но вас может удивить,
что он  допустим вообще. Что же здесь
происходит, действительно ли
компилятор видит   выражение int в
левой стороне оператора ==, видит
значение null в правой стороне и
знает,  что нужно неявное
преобразование в тип int? для каждого
из них. Поскольку сравнения  между
двумя значениями типа int? совершенно
допустимы, код не вызывает ошибки, только предупреждение.

В итоге, если мы упакуем значение вот так:
int i = 5; 
if ((object)i == null) 
{ 
   Console.WriteLine ("Never going to happen"); 
}

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


Ответы

Ответ 1



Немного подробностей - несмотря на то, что в исходниках действительно нет (object), оно есть в IL. public static void Test(T param) { if (param == null) { Console.WriteLine("Never going to happen"); } } после компиляции (в release!) в IL превращается в .method public hidebysig static void Test(!!T param) cil managed { // Code size 19 (0x13) .maxstack 8 IL_0000: ldarg.0 IL_0001: box !!T <---- IL_0006: brtrue.s IL_0012 IL_0008: ldstr "Never going to happen" IL_000d: call void [mscorlib]System.Console::WriteLine(string) IL_0012: ret } // end of method Program::Test Обратите внимание, что никаких вызовов == уже нет. Значение в стеке - адрес объекта в памяти - просто сравнивается с 0. Более того, boxing и проверка на null есть и после прохода JIT по коду при запуске под отладчиком: Test(5); 01212640 mov ecx,5 01212645 call dword ptr ds:[5191C7Ch] ---- if (param == null) 01212678 push ebp 01212679 mov ebp,esp 0121267B sub esp,8 0121267E xor eax,eax 01212680 mov dword ptr [ebp-8],eax 01212683 mov dword ptr [ebp-4],ecx 01212686 cmp dword ptr ds:[5190B94h],0 0121268D je 01212694 0121268F call 7431C310 01212694 mov ecx,73273B04h 01212699 call 01182100 0121269E mov dword ptr [ebp-8],eax 012126A1 mov eax,dword ptr [ebp-8] 012126A4 mov edx,dword ptr [ebp-4] 012126A7 mov dword ptr [eax+4],edx 012126AA mov eax,dword ptr [ebp-8] 012126AD test eax,eax 012126AF jne 012126BC { Console.WriteLine("Never going to happen"); 012126B1 mov ecx,dword ptr ds:[3D621F4h] 012126B7 call 731A023C } } 012126BC nop } } 012126BD mov esp,ebp 012126BF pop ebp 012126C0 ret а вот запуск без отладчика полностью меняет картину. Код вида Debugger.Launch(); Test(5); Test("test"); на самом деле превращается в Debugger.Launch(); 041D0050 push ebp 041D0051 mov ebp,esp 041D0053 call 7386868C Test("test"); 01260058 cmp dword ptr ds:[3C82188h],0 <-- это заинлайненный if 0126005F jne 01260076 01260061 call 731A0258 Нет никакого box. Нет if. Нет даже ни одного вызова Test. Вызов с 5 удален целиком. Вызов с string test - заинлайнен. Не заморачивайтесь и дайте JIT делать свою работу. И да, компилируйте Production сборки как Release, а не как Debug. Это сокращает жизнь переменных - и продлевает жизнь разработчика.

Ответ 2



Если вы действительно хотите сравнить с null, то сравнивайте на здоровье. Это предупреждение существует, так как подобное сравнение часто ошибочно. Будете ли вы приводить к object или сравнивать просто так — наверняка не имеет значения, потому что JIT-компилятор выкинет избыточные сравнения с null для value-типов. В исходнике CoreCLR простое сравнение: private int FindEntry(TKey key) { if( key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } // ... }

Ответ 3



Т. о. если я правильно понимаю, приведение к object нужно лишь для того чтобы не получить предупреждение? Подозреваю, что неправильно. Как уже написали, в исходниках этого приведения нет. Я думаю, что оно появляется в скомпилированном варианте в силу того, что generic-методы компилируются на основе информации, которая известна при компиляции, а не при запуске. Соответственно, там дан только generic-тип TKey, на который не накладывается никаких ограничений. Всё, что знает компилятор, что это некоторый object. Дальше он наталкивается на использование оператора ==, единственная применимая перегрузка которого принимает два объекта object. Чтобы вызвать этот оператор, key неявно приводится компилятором к object. Кстати, весьма вероятно, что jit-компилятор всё равно оптимизирует эту штуку, как происходит с кастом в IEnumerable при указании типа переменной в linq-запросе. PS: Это только мои представления о происходящем, я не проверял, действительно ли всё происходит именно так.

вторник, 26 ноября 2019 г.

Обязательно ли NULL в Си является указателем?


Вопрос на основе обсуждения ответа про sizeof NULL.

Вот куски стандарта:


  An integer constant expression with the value 0, or such an expression cast t
type void *, is called a null pointer constant  
  
  NULL which expands to an implementation-defined null pointer constant


Кажется, что не запрещено вместо #define NULL ((void*)0) сделать просто #define NUL
0, как это сделано в плюсах. Тогда получается, что sizeof NULL равен либо sizeof (void*), либо sizeof (int) (в варианте sizeof 0).

Однако, @Vlad from Moscow утверждает, что NULL в Си обязан быть указателем.


  NULL в С определен как указатель.
  
  Ключевое значение в приведенной цитате имеют слова "cast to type void *".
  
  Здесь или имеет отношение не к приведению типов, а к виду выражения. И именно таким образом определяется NULL.


Так допустимо ли для Си (без плюсов) иметь?

#define NULL 0


И если допустимо, то почему в компиляторах используется

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif


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


Ответы

Ответ 1



Просмотрев внимательно стандарт C, я думаю, что вы правы. null pointer constant это либо целочисленное константное выражение со значением 0, либо такое выражение, приведенное к типу void *. Поэтому определение макроса NULL может быть в принципе различным, зависящим от реализаци компилятора. То есть нигде в стандарте C я не нашел, что макрос NULL обязан быть определен как ( void *)0 Я заглянул в документ Rationale for International Standard— Programming Languages— C и там нашел следующее (7.17 Common definitions ) 25 NULL can be defined as any null pointer constant. Thus existing code can retain definitions of NULL as 0 or 0L, but an implementation may also choose to define it as (void*)0. This latter form of definition is convenient on architectures where sizeof(void*) does not equal the size of any integer type. Так как null pointer constant преобразуется в выражениях в null pointer, а разме null pointer может быть не равен размеру ни одному целочисленному типу, то удобнее определять null pointer constant как ( void * )0 то есть сразу же приводить ее к типу указателя. В C++ от такого определения отказались, так как в отличии от C в C++ в связи с обеспечение безопасности типов нужно явное приведение указателя на void к типу указателя на конкретный объект. То есть если null pointer constant NULL будет определена в C++ как ( void *)0 то вам придется делать явное приведение указателей, как, например, int *p = ( int * )( void *)0; что, естественно, очень обременительно. Поэтому в C++ Отказались объявлять NULL в виде целочисленной константы, приведенной к типу void *.

воскресенье, 24 ноября 2019 г.

Что из себя представляет null?



null - это экземпляр чего-то?
К какому типу принадлежит null?
Что такое null?
Как он представлен в памяти?

    


Ответы

Ответ 1



null - это экземпляр чего-то? Нет такого типа, которому бы соответствовал instanceof от null. 15.20.2 Type Comparison Operator instanceof RelationalExpression: RelationalExpression instanceof ReferenceType В рантайме результат оператора instanceof будет true, если значение RelationalExpression не null и ссылка может быть приведена к ReferenceType без получения исключения ClassCastException. Иначе результат будет false. Это означает, что для любого типа E и R, для любого E o, где o == null, o instanceof R будет всегда false. К какому типу принадлежит null? JLS 4.1 The Kinds of Types and Values. Есть также специальный тип - null, тип выражения null, у которого нет имени. И т.к. тип null не имеет имени, невозможно объявить переменную с типом null или привести переменную к типу null. null ссылка - единственное возможное значение выражение типа null . null всегда можно привести к любому ссылочному типу. В действительности, можно игнорировать тип null и притвориться, что null - это просто специальный литерал, который может быть любым ссылочным типом. Что такое null? Как сказано в цитате из JLS выше, можно считать, что "null - это просто специальный литерал, который может быть любым ссылочным типом". В Java null == null (что верно не для всех языков). Из описания java.lang.Object: public boolean equals(Object obj) Для любой не null переменной x, x.equals(null) должно возвращать false. null также является значением по умолчанию для всех ссылочных типов. JLS 4.12.5 Initial Values of Variables: Любой экземпляр класса, переменная или элемент массива специализируются значением по умолчанию: Для всех ссылочных типов (§4.3), дефолтное значение - null. Вы можете использовать это свойства для отложенной инициализации, когда поле буде иметь начальное значение null до тех пор, пока оно фактически не будет использовано, где будет заменено "реальным" значением (вычисление которого может быть дорогостоящим). Есть и другие применения. Если посмотреть на java.lang.System: public static Console console() Returns: The system console, if any, otherwise null. Это очень распространённая практика: null используется для обозначения несуществующего объекта. Другой пример - java.io.BufferedReader: public String readLine() throws IOException Returns: A String containing the contents of the line, not including any line-termination characters, or null if the end of the stream has been reached. readLine() будет возвращать instanceof String для каждой строки, пока не получи null, обозначающий конец. Это позволяет обрабатывать каждую строку следующим образом: String line; while ((line = reader.readLine()) != null) { process(line); } Примечание: пустая строка - не проблема, т.к. "" != null. Давайте рассмотри java.util.Map: V get(Object key) Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. If this map permits null values, then a return value of null does not necessaril indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases. Здесь мы видим, что использование null может усложнить дело. Тут говорится, что если такого ключа нет, будет возвращён null. Второе утверждение гласит, что даже если элемент по такому ключу есть, всё равно может вернуться null. К примеру, java.util.Hashtable делает вещи проще путём запрета null в ключах и значениях так что, если V get(Object key) вернёт null это однозначно говорит о том, что под таким ключом ничего нет. Операции автоматического боксинга/анбоксинга на null выбросят java.lang.NullPointerException: Integer i = null; // при анбоксинге null в integer будет выброшен NullPointerException int a = i; Если резюмировать, то null используется как специальное значение для обозначения: Не инициализированного состояния. Терминальное условие Несуществующий объект. Неизвестное значение. Как null представлен в памяти? Из спецификации JVM: The Java Virtual Machine specification does not mandate a concrete value encoding null. Небольшое дополнение Интересная цитата C.A.R Hoare: I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. Так же можно посмотреть презентацию про ошибку на миллиард долларов.

суббота, 22 июня 2019 г.

Почему родитель формы равен Null?

Выдержка кода из главной формы:
public partial class Main : Form { public NetComm.Host server; public NetComm.Client client; public Main() { InitializeComponent(); } private void ToolStripMIConnect_Click(object sender, EventArgs e) { ClientPref formConnect = new ClientPref(); formConnect.Owner = this; formConnect.ShowDialog(); } }
Выдержка кода из создаваемой формы:
public partial class ClientPref : Form { Main formMain; public ClientPref() { InitializeComponent(); formMain = this.Owner as Main; } private void btnConnect_Click(object sender, EventArgs e) { if (formMain != null) //следующий бок не выполняется т.к. formMain равен null { if (IsAddressValid(this.txtBoxIP.Text)) { formMain.ClientStart(Convert.ToInt32(this.txtBoxPort.Text), this.txtBoxIP.Text, "Соперник"); } else toolTipClient.Show("Введен некорректный IP-адрес.", this.txtBoxIP); }
}
Почему formMain равен null или как в итоге обратиться к методу ClientStart формы Main?


Ответ

Простой вариант - переписать вот так, сделать поле вычислимым свойством:
Main FormMain { get { return this.Owner as Main } };
Тогда в btnConnect_Click всё должно работать.

среда, 24 апреля 2019 г.

Java типы, допускающие NULL значения

Существуют ли в Java что-то вроде типов, допускающих NULL значения? То есть с сервера приходит, на пример, int, но вместо него может прийти NULL В C#можно это обработать, используя типы, допускающие, NULL значения.
int? a = NULL;
Как это следует обрабатывать в Java?


Ответ

В Java ссылка любого класса может принимать значение null Примитивы же...
Целые числа - byte, short, char, int, long Числа с плавающей точкой (иначе дробные) - float, double Логический - boolean
...лишены такой возможности.
Если требуется создать ссылку на один из примитивных типов данных, необходимо использовать соответствующий класс-обертку. Также в таких классах есть некоторые полезные методы и константы, например минимальное значение типа int можно узнать использовав константу Integer.MIN_VALUE. Оборачивание примитива в объект называется упаковкой (boxing), а обратный процесс распаковкой (unboxing). (источник)
Соответственно для решения вашей задачи необходимо использовать классы-обёртки примитивов: int -> Integer, short -> Short и т.п.

воскресенье, 7 апреля 2019 г.

как правильно сравнивать с null?

как правильно писать
if(obj ==null){}
или
if(null == obj){}
в чем разница?
и вторая часть вопроса можно ли это заменить на
static boolean isNull(boolean val)
или нет? и что это такое?


Ответ

Особой разницы нет. Но есть не особая. Вариант с
if(null == obj)
уберегает вас от распространенной ошибки типа
if(obj = null)
то есть случая, когда вместо сравнения по недосмотру выполняется присваивание. В случае с null = obj компилятор сообщит об ошибке, поскольку нельзя присваивать что-то null'у. Все вроде бы здорово, и отличие вроде бы и правда есть. Но это замечательное преимущество практически не имеет никакой ценности, поскольку будет работать только в том случае, если obj имеет тип Boolean. В остальных случаях компилятор тоже догадается сообщить об ошибке, поскольку результат такого присваивания не может быть приведен к булевскому типу. Поэтому в java разницы нет практически никакой. А вот в Си это может иметь смысл - там NULL - это всего лишь макрос, под которым обычно скрывается 0, и ошибки типа
if(obj = NULL)
довольно распространены, так как присваивание указателю NULL'а вполне допустимо, а случайно написать = вместо == - дело нехитрое. Может такая ошибка случиться и в JavaScript в силу того, что типизация в нем динамическая, и любую переменную можно неявно привести к булевскому значению. С другой стороны, в js чаще принято писать условия типа
if(!obj)
вместо
if(obj == null)
хотя так делают далеко не все. На мой взгляд конструкция типа null == obj выглядит коряво, но это дело вкуса.

четверг, 20 декабря 2018 г.

nil в языках программирования

Меня интересует как используется nil в различных языках программирования, и вообще насколько это удобно с точки зрения дизайна языка. В данной теме обсуждаются исключительно динамически типизированные языки. К примеру в JavaScript есть два значения похожих на nil: null и undefined. (Нужно ли было плодить?) В Scheme есть #f, но нет nil. В Factor тоже нету nil, и используют f. (Нужен ли nil если есть false?) Кроме того, как-то читал что вроде в первых версиях Objective-C был объект nil (Objective-C не знаю вообще), который поглощал вызовы, был чёрной дырой. Т.е. вёл себя как NaN для чисел. На любое сообщение объект возвращал nil Насколько допустимо трактовать "пустые" объекты (пустой список, пустой словарь и т.д.) как false, и есть ли у такой фичи опасные побочные эффекты, или вообще расположение к каким-либо багам. Приветствуются: Ссылки на статьи с обзором различных подходов Ссылки на какие-либо интересные языки, относящиеся к теме Обсуждение различных подходов, удобства и опасности использования


Ответ

NULL нужен там, где есть ссылки. Почему в JavaScript есть еще undefined? Потому что null и undefined - разные объекты. По умолчанию ссылка указывает на undefined, а null - элемент логики. Сейчас ссылка null, потом не null. Скажем так, в С/С++ указатели тоже не null изначально, а undefined. Пустые объекты - не false, потому что false - семантическое значение. Элемент логических выражений, а null - нет (несмотря на то, что он используется в них, он преобразовывается). Если кратко, то наличие пустого объекта обязательно для языков со ссылками, а false - это отдельный объект, такой же как строка "ABC" или 4 - т.е. непустой, а семантически значимый.

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

Язык C, malloc() и NULL

Хотел бы обсудить, что делать, если malloc() вернул NULL
Ясное дело, что если ведется разработка библиотеки или какого-либо "кирпичика", который будет использоваться неизвестно в каких условиях, то вызов abort() совершенно неприемлем.
Например, если пишется загрузчик ресурсов из файла в оперативную память, то лучше бы сказать "извини, памяти нет", чем внезапно упасть неизвестно по какой причине.
1) От людей, которые пишут под Linux, слышал, что они не проверяют результат вызова malloc(), обосновывая это тем, что malloc() запросто может вернуть не NULL, а при попытке обращения к памяти через такой указатель все рухнет. Ибо память изначально выделяется в виртуальной области, и такая память не связывается с реальной до тех пор, пока к ней не произойдет обращение.
В Windows 10/8/7/Em/CE тоже так?
2) Если память выделяется маленькими порциями, то может произойти такое, что при неудачном malloc() мы хотим сделать printf(), не говоря уже про fopen() + fwrite(), а памяти нет, и все совсем падает. Это реально?
3) Довольно долго думал над тем, как защитить программу, которая крутится на ПЛК, обеспечивая передачу телеметрии. Сам объект находится далеко - "три для ехать, а потом еще на вертолете с пересадками". В общем, классический пример промышленных систем.
Приложение многопоточное. Одни потоки отправляют/получают данные, другие устанавливают соединение по TCP, третьи - ведут файлы журналов. Много библиотек, часто - закрытых в dll.
В итоге пришел к выводу, что единственное адекватное решение, если malloc() где-то вернул NULL, - это вызов abort(), с последующей скорой перезагрузкой отдельным модулем, который определил, что наше приложение перестало "теребить" какой-то сигнализатор. Защита журналов в этом случае делается тривиальным способом - при каждом старте процесса создается новый журнал. Таким образом, старые журналы могут иметь в конце какой-то недописанный "хвост", но это нестрашно.
Ни разовое аллоцирование, ни "умный" управляющий памятью, - не дают гарантий, а лишь приводят к сингулярному разрастанию возможных неопределенных поведений.
4) Я так понимаю, на исчерпывающем большинстве промышленных устройств сторожевой таймер предусмотрен как раз из-за того, что существуют вот такие неразрешимые программным путем проблемы, как неудачный malloc() и исчерпание памяти?


Ответ

malloc всегда должет возвращать адрес, если malloc вернул null тогда это аварийная ситуация. Вы можете 1) Прекратить работу всей программы. 2) Вы можете прекратить работу "процесса" (не процесса как Process, а класса, библиотеки, функции или набора функций, например процесс компрессии данных), который не может завершить свою работу, освободить память занимаемую этим "процессом". Тут я солидарен с alexolut
Надо не забывать что у программы есть детерминированое - определённое поведение, и недетерминированое. Нужно стремится делать детерминированое поведение. В программе желательно до начала процесса (а не вовремя) опеределить сколько нужно на процесс памяти, и не начинать процесс (или работу программы) если его не возможно выполнить. Нужно ограничивать аппетиты пользователя, если вам вместо 100Мб прислали 10Gb, то сообщить что файлы/потоки свыше 2Gb ваша программа не обрабатывает (условно, для примера). Нужно определить заранее на какие обьёмы вы хотите расщитывать, и проверять есть ли они (обьемы) в наличии. Если вы резервируете память столько незнаю сколько - то для небольших программ будет всё ок, а для больших - может закончится нехваткой памяти. Т.е. невозможно сделать malloc столько незнаю cколько.
В ответственных за скорость местах лучше использовать пулы памяти. Недостаток пулов - часть "неиспользуемой" памяти просто лежит.
Нужно соблюдать несколько правил... и тогда у вас память не закончится неожиданно.
резервировать в ответственных местах память заранеее ограничивать глубину рекурсии в рекурсивных функциях (хвостовая рекурсия - отдельный случай) соблюдать порядок malloc и free. Желательно разделять память для долгого использования и для кратковременного. Сколько зарезервировано паралельными процессами. Проверять наличие утечки памяти (когда malloc больше чем free, но для этого нужно знать какое число, разница колличества вызовов malloc/free должна быть, и для этого нужно знать сколько памяти долгого использования зарезервировано). делать пулы, или массивы памяти, для возможности освободить память по завершению "процесса" которую будет использовать конкретный процесс (действие) (упростит освобожнение). память может закончится в двух случаях а) неправильно построеная "модель памяти" (неопределённое поведение программы которое не предусмотрено) б) пользователь использует компьютер с реально маленьким размером оперативки и файлом подкачки. Второй случай редкий, но проверить его на старте стоит.
Ещё хочу заметить, что ОС уровня "приложение" оперирует виртуальной памятью, поэтому может такое быть, что ваше приложение занимает меньше памяти чем вы резервировали. Тут Язык C, глобальные массивы часный случай когда реально приложение занимает меньше памяти, чем виртуально.
Если для вас важна стабильность - резервируйте при запуске нужное количество памяти - и проблемы с malloc у вас не будет. Ведь главный вопрос не в том хватает памяти или не хватает, а в том - сколько вам нужно.