c null pointer
Понятно, что можно сделать что-нибудь вроде (void *)1, или взять ссулку на какую-нибудь ненужную переменную или функцию внутри программы, или просто тупо malloc сделать лишний. Но я подумал, что может какие специальные методики для такого есть?Вроде ты перечислил все специальные методики
Вроде ты перечислил все специальные методикиТогда вопрос в том какая из них предпочтительней?
Вроде ты перечислил все специальные методикиможно ещё выравнивать данные и использовать младшие биты
можно ещё выравнивать данные и использовать младшие битыНу это может плохо сказаться на переносимости кода и будет зависеть от флагов компилятора. Вот и с (void *)1 у меня тоже подозрения есть, что вдруг где-нибудь такой указатель возможен.
(все отсылки к реальным персонажам суть симулякры)
глянь на include/linux/err.h
Понятно, что можно сделать что-нибудь вроде (void *)1Ну и сделай указатель на любое aligned значение от 0 до (PAGE_SIZE - 1) - в этом диапозоне у тебя гарантированно ничего нет и не будет, ибо работа с VMM постраничная. По поводу malloc и прочих ненужных мыслей - ты же не собираешься разыменовывать, верно?
ты же не не собираешься разыменовывать, верно?вот-вот, в случае (void *)1 еще и контроль за ошибками со стороны системы получается.
Верно, только не надо использовать (void *)1, ибо unaligned - по мне так лучше получить штатную ошибку от VMM, чем data abort на коре.
можно сделать что-нибудь вроде (void *)1Больше быднокодинга! Ты можешь круче, мы знаем.
Вместо того, чтобы сделать грамотный и понятный интерфейс, сделай нечто, что обломает лучшие умы будущего.
глянь на include/linux/err.hдля тех кому лень смотреть. в ядре широко используется такая конструкция
#define IS_ERR_VALUE(x) unlikelyx) >= (unsigned long)-MAX_ERRNO)
static inline void *ERR_PTR(long error)
{
return (void *) error;
}
static inline long PTR_ERR(const void *ptr)
{
return (long) ptr;
}
static inline long IS_ERR(const void *ptr)
{
return IS_ERR_VALUEunsigned long)ptr);
}
от 0 до (PAGE_SIZE - 1) - в этом диапозоне у тебя гарантированно ничего нет и не будет, ибо работа с VMM постраничнаяА 0-я страница гарантированно не может быть использована?
для тех кому лень смотреть. в ядре широко используется такая конструкцияСпасибо за то, что запстил, а то я в /usr/include не нашёл файла и чуть было не расстроился, а это в сорцах ядра оказывается.
Действительно испльзуют, прикольно. А что это за ERR pointer? Он вообще на реальную память указывает когда-нибудь?
PS. Кстати в ядре всё же с большим успехом можно гарантировать какие страницы не используются.
Больше быднокодинга! Ты можешь круче, мы знаем.Ну давай подробнее расскажи-то что тебя смущает?
Вместо того, чтобы сделать грамотный и понятный интерфейс, сделай нечто, что обломает лучшие умы будущего.
А 0-я страница гарантированно не может быть использована?Не гарантировано, туда можно отмапить файл или кусок памяти, но обычно так не делают.
Ну давай подробнее расскажи-то что тебя смущает?Да я просто прикалывался.
Если говорить по делу, то лучше интерфейсы сделать явными и код передавать как-то ещё.
Бля, это где так можно? В винде что ли?А 0-я страница гарантированно не может быть использована?Не гарантировано, туда можно отмапить файл или кусок памяти
Если говорить по делу, то лучше интерфейсы сделать явными и код передавать как-то ещё.Какие интерфейсы? Какой код?
Мне просто нужно избавиться от лишнего флага, который будет место занимать лишнее и им менее удобно пользоваться будет. Возможно его проверка ещё и медленнее будет.
эх, правильно в яве сделали с их списком исключений
Бля, это где так можно? В винде что ли?В Linux под root можно:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
int id;
void* ptr;
char* pc = NULL;
int i;
id = shmget(IPC_PRIVATE, 4096, IPC_CREAT|0666);
if (id == -1)
{
perror("shmget");
return -1;
}
printf("shmget: %d\n", id);
ptr = shmat(id, 1, SHM_RND);
if (ptr == (void*)-1)
{
perror("shmat");
return -1;
}
printf("shmat: %x\n", ptr);
memset(ptr, '5', 4096);
for (i = 0; i < 4096; i++)
if (*pc++ != '5')
return -1;
printf("OK\n");
return 0;
}
Спасибо за информацию, буду знать.
Последней страницы в ядре нет, т.к. часто используется container_of и надо ловить баги при дереференсе поинтеров полученных применением его к NULL. В юзерспейсе этой страницы тоже быть не должно.
К сожалению долбоёбы пишущие и пакетирующие wine во многих дистрибутивах положили к нему sysctl config молча вырубающий эту защиту под предлогом совместимости с уёбищьным виндовым стофтом, в котором чинили баги типа null-pointer dereferrence замапливанием этой самой нулевой пэйджи =)
Хочу в C себе сделать ещё один указатель "в никуда". Чтобы можно было делать как-нибудь так:Если слова "хороший стиль программирования" тебе о чем-нибудь говорят, то "ты не должен этого хотеть" (c)
Лучше заведи дополнительную переменную.
Если заменять настоящий дизайн кода каким-то стандартным набором шаблонов и фанатичной верой
в универсальность и непогрешимость этих шаблонных решений получаются уродливые раздутые спагетти.
новую переменную для состояний ошибки заводить не нужно, для этого уже есть errno.
новую переменную для состояний ошибки заводить ненужно, для этого уже есть errno.Заметь, топикстартер ничего не говорил про то, что этот NULL2 нужен для отслеживания "состояния ошибки" в программе.
Получается, как в анекдоте:
...
return NULL; // not NULL, really
...
Это такой стандартный способ вернуть errno вместо поинтера — в ядре ошибки всегда отрицательные.Т.е. вместо (void *)1 лучше использовать (void *)-1?
Последней страницы в ядре нет, т.к. часто используется container_of и надо ловить баги при дереференсе поинтеров полученных применением его к NULL. В юзерспейсе этой страницы тоже быть не должно.
(void *)-1лучше позаботиться, чтобы эта -1 знаково расширилась: (void *)-1LL или (void *ptrdiff_t)-1
(что в тех ядерных макросах достигается за счёт того, что err типа long; видимо, на IL32P64 оно не рассчитано)
в записях типа IL32P64 буквы соответствуют типам: int, long — 32-bit, pointer — 64 bit
Если слова "хороший стиль программирования" тебе о чем-нибудь говорят, то "ты не должен этого хотеть" (c)А мне как раз кажется, что если у меня 3 состояния: "память ещё не выделена", "память не надо выделять", "память выделена", то заводить дополнительную переменную (что приведёт к 4 возможным состояниям) — это и есть нехороший стиль. Потом т.к. их будет много, то экономия места в 2 раза уже как минимум оправдывает данный подход. Всё это мне нужно не в том месте, где можно насрать на память, на производительность, зато будет шаблонно.
Лучше заведи дополнительную переменную.
все так говорят.
> Откуда стремление все обобщать?
работа такая...
Речь идет о конкретной проблеме. Заметь, топикстартер ничего не говорил про то, что этот NULL2 нужен для отслеживания "состояния ошибки" в программе.ну вот всегда так, не разобравшись что конкретно нужно начинают строить всеобщее универсальное решение.
А мне как раз кажется, что если у меня 3 состояния: "память ещё не выделена", "память не надо выделять", "память выделена", то заводить дополнительную переменную (что приведёт к 4 возможным состояниям) — это и есть нехороший стиль. Потом т.к. их будет много, то экономия места в 2 раза уже как минимум оправдывает данный подход. Всё это мне нужно не в том месте, где можно насрать на память, на производительность, зато будет шаблонно.Для этого мне не нравиться применять non-NULL т.к. при освобождении придётся думать нужно ли освобождать память.
Лучше выделять именно там где становиться понятно что память нужна, иначе оставлять NULL, а free пофиг на NULL.
Для этого мне не нравиться применять non-NULL т.к. при освобождении придётся думать нужно ли освобождать память.Освобождаться оно будет по exit . А моменты нужно и можно разнесены по времени. У меня строится каркас дерева, где сразу будут отсечены заранее ненужные ветви, а потом уже в динамике достраиваются ветви по входным данным там где можно.
Лучше выделять именно там где становиться понятно что память нужна, иначе оставлять NULL, а free пофиг на NULL.
Для этого мне не нравиться применять non-NULL т.к. при освобождении придётся думать нужно ли освобождать память.
Лучше выделять именно там где становиться понятно что память нужна, иначе оставлять NULL, а free пофиг на NULL.
ну вот всегда так, не разобравшись что конкретно нужно начинают строить всеобщее универсальное решение.
А мне как раз кажется, что если у меня 3 состояния: "память ещё не выделена", "память не надо выделять", "память выделена", то заводить дополнительную переменную (что приведёт к 4 возможным состояниям) — это и есть нехороший стиль. Потом т.к. их будет много, то экономия места в 2 раза уже как минимум оправдывает данный подход. Всё это мне нужно не в том месте, где можно насрать на память, на производительность, зато будет шаблонно.Как вариант, "память не нужно выделять" - NULL, "память не выделена" - запись в глобальную переменную, например, ту же errno.
Как вариант, "память не нужно выделять" - NULL, "память не выделена" - запись в глобальную переменную, например, ту же errno.Одну для всех? У меня же будет куча таких указателей.
У меня строится каркас дерева, где сразу будут отсечены заранее ненужные ветви, а потом уже в динамике достраиваются ветви по входным данным там где можно.И для этого ты делаешь специальный NULL? Я согласен с -ом:
И для этого ты делаешь специальный NULL? Я согласен с -ом:Какие у тебя предложения?
Какие у тебя предложения?Не надо извращений типа (void*)-1 и подобных.
Размести полноценную вершину дерева, чтобы указатель показывал на соответствующий ему тип данных.
Размести полноценную вершину дерева, чтобы указатель показывал на соответствующий ему тип данных.Ничего не понял. А так у меня указатели на несоответствующие типы будут указывать что ли?
Какие вершины дерева в твоём понимании "полноценные"?
В порядке бреда: во всяких деревьях и списках бывает удобно в завершающих элементах ставить указатель на себя. Это может еще и упростить код в некоторых местах. Вдруг применится в твоем случае.
В порядке бреда: во всяких деревьях и списках бывает удобно в завершающих элементах ставить указатель на себя. Это может еще и упростить код в некоторых местах. Вдруг применится в твоем случае.Идея прикольная, но мне кажется, что "p == SKIP_POINTER" всё же нагляднее, чем "p == q". Ну и потом при копировании проблем не возникнет, хотя мне сейчас это вроде не нужно.
Ничего не понял. А так у меня указатели на несоответствующие типы будут указывать что ли?А так у тебя они будут указывать на хер знает что.
А так у тебя они будут указывать на хер знает что.Почему. У меня есть указатель типа (struct tree *) и у него будет 3 варианта значений: NULL, SKIP, и на аллоцированную память. Где здесь "хер знает что"?
Почему. У меня есть указатель типа (struct tree *) и у него будет 3 варианта значений: NULL, SKIP, и на аллоцированную память. Где здесь "хер знает что"?SKIP, если это (void*)-1
SKIP, если это (void*)-1И что в этом плохого? NULL ведь тоже указывает на хер знает что.
И что в этом плохого? NULL ведь тоже указывает на хер знает что.NULL как раз никуда не указывает.
NULL как раз никуда не указывает.Это потому что мы так считаем. Но технически он такой же указатель как и все. А я буду заодно считать, что SKIP тоже никуда не указывает.
А я буду заодно считать, что SKIP тоже никуда не указывает.В таком случае говорят, что жираф большой, ему виднее.
Но лучше всё равно писать нормальный читаемый код без хаков, тем более даже Си это позволяет.
Но лучше всё равно писать нормальный читаемый код без хаков, тем более даже Си это позволяет.Я уже писал вроде почему я считаю, что этот код будет читаемый и более нормальный. Тем более, что это наружу никуда не экспортируется. Просто внутреннее соглашение с самим собой.
Тем более если проверки обернуть define-ами или функциями, то вообще не будет никакой разницы в читаемости.
В таком случае говорят, что жираф большой, ему виднее.Я считаю, что ты не привёл никаких весомых аргументов "против". Только какие-то мифические предположения, что код будет нечитаемым, что-то будет некрасиво (непонятно по каким критериям) и т.п.
Я считаю, что ты не привёл никаких весомых аргументов "против". Только какие-то мифические предположения, что код будет нечитаемым, что-то будет некрасиво (непонятно по каким критериям) и т.п.Просто я как бы привык к тому, что код приходится расширять и поддерживать.
Для меня это уже достаточные причины для того, чтобы писать без использования хаков.
NULL - это общепринятый стандарт для указателя, который никуда не показывает.
Если написать SKIP как (void*)-1, то это будет хаком: не NULL указатель на память, к которой нельзя обращаться.
Если написать SKIP = create_some_node то код будет намного лучше, безопасней и проще для развития: эту память можно читать и передавать в разные функции, можно добавлять какую-то логику и значения по умолчанию, и так далее.
Если написать SKIP = create_some_node то код будет намного лучше, безопасней и проще для развития: эту память можно читать и передавать в разные функции, можно добавлять какую-то логику и значения по умолчанию, и так далее.безопаснее? не нужно эту память читать. это всё равно что NULL читать
эту память можно читать и передавать в разные функцииДа не будет оно наружу выдаваться.
Это внутреннее устройство дерева. Снаружи в него будут только ложить данные и вынимать. Прямой доступ к вершинам не предполагается.
Если и понадобится расширить, то разве только ещё каким-нибудь NULL-ом. В общем польза от добавления лишних сущностей неочевидна даже в будущем, а вот польза от их исключения есть, как я уже говорил — меньше памяти занимает и проще проверки будут в некоторых местах.
безопаснее? не нужно эту память читать. это всё равно что NULL читатьТы прочитал всё предложение, которое привёл в качестве цитаты?
Потому что там написано, что не всё равно: например, там могут быть данные по умолчанию.
Да не будет оно наружу выдаваться.Я ещё раз говорю: неважно, будет выдаваться наружу или нет.
Это внутреннее устройство дерева. Снаружи в него будут только ложить данные и вынимать. Прямой доступ к вершинам не предполагается.
Если и понадобится расширить, то разве только ещё каким-нибудь NULL-ом. В общем польза от добавления лишних сущностей неочевидна даже в будущем, а вот польза от их исключения есть, как я уже говорил — меньше памяти занимает и проще проверки будут в некоторых местах.
Код надо писать так, чтобы его можно было расширять и поддерживать.
Иначе я вообще не вижу смысла создавать тему на форуме. Как я уже сказал, жираф большой, и ему виднее.
Предложенное мною решение не занимает больше памяти в зависимости от размера дерева.
И проверки там точно такие же, а то и более безопасные, потому что не нарушают общепринятых соглашений.
Предложенное мною решение не занимает больше памяти в зависимости от размера дерева.Ну я про это решение писал вначале. Оно менее удобное, чем константа, поэтому нужны причины для его использования, про которые я и спрашивал.
И проверки там точно такие же, а то и более безопасные, потому что не нарушают общепринятых соглашений.
Проверки как раз не будут нарушать никаких соглашений общепринятых. Они нарушатся, только если эти ссылки использоваться будут. Для последующего развития тоже не вижу проблем особых. Тем более, что не известно какое оно будет, а усложнять программу ради неведомого будущего бессмыссленно.
Оно менее удобное, чем константа, поэтому нужны причины для его использования, про которые я и спрашивал.Теперь я не вижу аргументов.
Моё решение вообще-то тоже с константой.
Проверки как раз не будут нарушать никаких соглашений общепринятых.Общепринятые соглашения нарушает неправильное значение указателя на память.
Тем более, что не известно какое оно будет, а усложнять программу ради неведомого будущего бессмыссленно.Что действительно является усложнением программы, так это хаки.
Что действительно бессмысленно, так это писать хаки ради неведомой выгоды.
Теперь я не вижу аргументов.Константа, которая появляется во время выполнения?
Моё решение вообще-то тоже с константой.
Общепринятые соглашения нарушает неправильное значение указателя на память.Ну я и говорю, что разыменование нарушается, да.
Что действительно является усложнением программы, так это хаки.Да это не более хаки, чем заведение дополнительного флага или ненужной области памяти.
Что действительно бессмысленно, так это писать хаки ради неведомой выгоды.
Преждевременная оптимизация — корень зла, да.
Но я вот не знаю, что конкретно пишет , может там очевидная ситуация какая-то.
может там очевидная ситуация какая-то.Да, там очевидно, что в этом месте нужно быстродействие и памятью не стоит разбрасываться лишний раз тоже. Так бы я эту прогу на perl-е написал. Вернее даже так, я написал на perl-е, но оказалось очень медленно. Переписал на C, получилось в 100 раз быстрее.
Константа, которая появляется во время выполнения?Да. бывают константы, которые хранятся в памяти. В Си можно проинициализировать константу один раз.
Да это не более хаки, чем заведение дополнительного флага или ненужной области памяти.
Я опять не вижу аргументов. Заведение флага или размещение памяти делаются обычными языковыми конструкциями.
Да. бывают константы, которые хранятся в памяти. В Си можно проинициализировать константу один раз.Но зачем, если можно взять константу ещё во время компиляции. Может даже оптимизация какая будет благодаря этому.
Я опять не вижу аргументов. Заведение флага или размещение памяти делаются обычными языковыми конструкциями.Приведение типа — тоже обычная языковая конструкция. Какая разница? Ты вот используешь слово "хак", как аргумент, но почему это "хак"?
Ты вот используешь слово "хак", как аргумент, но почему это "хак"?Потому что ты вкладываешь смысл в указатель на несуществующую память.
Потому что ты вкладываешь смысл в указатель на несуществующую память.И в этом нет ничего плохого. Указатель в данном случае — это не ракетная наука, а простое число. В каких-нибудь более высокоуровневых языках может быть и некрасиво так вести себя с указателями, но в данном случае я не вижу ничего плохого.
Как я уже сказал NULL тоже указывает на несуществующую область, но когда все об этом договаривались, то не считали, что это страшный хак и тебя это тоже не смущает. В данном случае я делаю точно такой же хак как сделали для NULL, но только для себя.
И в этом нет ничего плохого.Что в этом плохого уже написано выше по теме.
Причём разными людьми и различными словами.
Что в этом плохого уже написано выше по теме.Прости, но я не заметил ничего конкретного.
Причём разными людьми и различными словами.
Прости, но я не заметил ничего конкретного.Прощаю. Не все могут принимать аргументы других людей.
Не все могут принимать аргументы других людей.Согласен.
В общем твоя позиция-то мне ясна, но для меня она неприменима в данном случае. Я пытался объяснить почему. Ну да ладно, полезных советов тут тоже было немало.
Общепринятые соглашения нарушает неправильное значение указателя на память.Какие нафиг общепринятые соглашения нарушаются? У тебя shmat (CONFORMING TO: SVr4, POSIX.1-2001.) возвращает (void *)-1 в случае ошибки, и нормальный указатель в случае успешного выполнения.
Так что никаким хаком это не является. Более того, (void *)-1 очень хорошо удовлетворяет поставленной задаче, поскольку:
1) является одним из значения типа переменной
2) разыменование (некорректное) данного указателя приводит к сегфолту сразу же, а не в другом конце программы
3) гарантированно не совпадает с другими валидными значениями переменной
4) существует в любой момент работы прогаммы
5) никогда не меняется и всегда может быть сравнено на равенство
Какие нафиг общепринятые соглашения нарушаются?Не использовать указатели на несуществующую память для реализации логики программы.
Не использовать указатели на несуществующую память для реализации логики программы.Это откуда такое?
Не использовать указатели на несуществующую память для реализации логики программы.а NULL что такое?
Во-вторых, (void *)-1, так же как и NULL, используются как специальные значения указателя, причем нередко (например, все программы использующие shmat).
В-третьих, попробую развить идею, что (void *)-1 удобнее в отладке и дальнейшем сопровождении программы, чем предложенный тобой метод.
Любая некорректная операция с ним сразу приводит к сегфолту, что особенно хорошо, когда приходится дописывать программу N-лет спустя. В твоем случае (SKIP = create_some_node поведение будет непредсказуемым: скорее всего будишь зацикливаться или, что гораздо хуже, начинать растить дерево из этой фиктивной вершины.
(void *)-1 хорошо заметен в дебагере. В твоем варианте SKIP по внешнему виду не отличим от любого другого указателя.
Когда ты пишешь SKIP = create_some_node ты выбираешь поистине рандомное значение в качестве специального значения указателя.
всё же это костыль. Но типично-сишный костыль. Будь разговор о яве, я бы рекомендовал просто оформить правильные исключения. А для си - это нормальная практика. Всё - ради производительности, и выдача результата через (void*) (-1) отвечает этой задаче
А для си - это нормальная практика. Всё - ради производительности, и выдача результата через (void*) (-1) отвечает этой задачеНу в случае C и возврата из функции это не только ради производительности полезно. Надо же как-то совмещать ошибки и легитимный возврат, если по каким-то причинам errno не подходит. А возврат двух значений будет сделан через ещё больший костыль, чем это.
Во-вторых, (void *)-1, так же как и NULL, используются как специальные значения указателя, причем нередко (например, все программы использующие shmat).
Да, (void*)-1 используется, чтобы проверить на ошибку.
Для написания логики хаки использовать не принято.
NULL является стандартным указателем в никуда (документированным в стандарте и используется обычно соответствующим образом, в хорошем коде на него не возлагается никаких специальных функций.
Любая некорректная операция с ним сразу приводит к сегфолту, что особенно хорошо, когда приходится дописывать программу N-лет спустя.Чем больше хаков, тем тяжелее что-то дописывать и развивать N лет спустя. Если эта, в общем-то, очевидная вещь кому-то не понятна, то я не вижу смысла продолжать дискуссию.
Это откуда такое?Мне бы было намного интереснее узнать, откуда обратное.
а NULL что такое?
A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function type. Two null pointer values of the same type shall compare equal.Правда это из стандарта плюсов, но всё равно даёт право говорить о том, что это общепринятая вещь. Указатель (void*)-1 стандартом языка не описан.
Мне бы было намного интереснее узнать, откуда обратное.Правило либо есть, либо нет. Чтобы его не было — это не надо придумывать ничего. А ты утверждал, что оно есть, так что тебе доказывать. А "обратного" доказывать не надо.
Мне бы было намного интереснее узнать, откуда обратное.Твоё утверждение:
Не использовать указатели на несуществующую память для реализации логики программы.не будучи как-то обосновано, пока что похоже на какую-то догму, а не рациональную рекомендацию. Кстати, что скажешь насчёт std::vector<T>::end он же тоже указывает на несуществующую память?
Правда это из стандарта плюсов, но всё равно даёт право говорить о том, что это общепринятая вещь. Указатель (void*)-1 стандартом языка не описан.А ты в своей программе реализовал класс. Он же тоже не общепринятый изначально и в стандарте не описан.
Так же и (void *)-1 в каждой программе не нужен. Зачем его описывать в стандарте?
Чем больше хаков, тем тяжелее что-то дописывать и развивать N лет спустя. Если эта, в общем-то, очевидная вещь кому-то не понятна, то я не вижу смысла продолжать дискуссию.Опять какие-то лозунги. Ты два моих предыдущих поста до конца дочитал? Я там как раз объяснял почему в данном случае решение с (void *)-1 с точки зрения сопровождения и развития программы в дальнейшем лучше.
не будучи как-то обосновано, пока что похоже на какую-то догму, а не рациональную рекомендациюВыше по теме я уже обосновал это. Почитай внимательно.
Ты два моих предыдущих поста до конца дочитал? Я там как раз объяснял почему в данном случае решение с (void *)-1 с точки зрения сопровождения и развития программы в дальнейшем лучше.Дочитал. Но не понял, почему сегфолт чем-то особенно хорош через N лет. А если совсем особенно дописать код так, что сегфолты будут только у клиентов раз в месяц...
Что касается дебагеров, оба моих дебагера способны в вотч листе вычислять выражения типа true/false, так что не знаю. Может быть, было бы полезно в каких-то случаях, но впиливать хаки в код ради этого я бы не стал.
Гарантированного несовпадения с (void*)-1 может и не быть, особенно при кроссплатформенной разработке.
Правило либо есть, либо нет. Чтобы его не было — это не надо придумывать ничего. А ты утверждал, что оно есть, так что тебе доказывать. А "обратного" доказывать не надо.Доказывать, что использовать указатель на несуществующую память плохо - не надо, потому что такие указатели описаны в стандарте со словами "undefined behavior". А вот если кто-то говорит, что можно и это безопасно - тогда пусть потрудится обосновать.
потому что такие указатели описаны в стандарте со словами "undefined behavior".Дай ссылку где там написано, что _сравнение_ таких указателей "undefined behavior".
Но не понял, почему сегфолт чем-то особенно хорош через N лет. А если совсем особенно дописать код так, что сегфолты будут только у клиентов раз в месяц...Мне кажется что ты проецируешь всё на свою область, в которой чаще программируешь. Но потребности и распределение приоритетов могут отличаться.
Undefined behaviour иногда очень undefined. Я один раз переписывал программку из Рихтера, которая работает с реестром, на unicode. И где-то забыл-таки правильной функцией воспользоваться. Так оно мне снесло кучу ключей из CLSID, я прифигел, ни один exe'шник запустить из проводника нельзя было. В итоге уже не помню, как выкрутился, взял сохранение реестра у коллеги и как-то применил его. так что уж лучше segfault'ы
Дочитал. Но не понял, почему сегфолт чем-то особенно хорош через N лет. А если совсем особенно дописать код так, что сегфолты будут только у клиентов раз в месяц...Сегфолт хорош тем, что возникает там, где произошла неокрректная операция. В бэктрейсе сразу видно, что случилось и где.
Что касается дебагеров, оба моих дебагера способны в вотч листе вычислять выражения типа true/false, так что не знаю. Может быть, было бы полезно в каких-то случаях, но впиливать хаки в код ради этого я бы не стал.
Гарантированного несовпадения с (void*)-1 может и не быть, особенно при кроссплатформенной разработке.
Чтобы вычислить какое-то выражение в вотч-листе, тебе нужно помнить, с чем сравнивать. (void *)-1 заметен и в том случае, если ты этого не помнишь.
То есть: сегфолт -> смотрим в дебагере бактрейс -> разыменовали -1 -> а, я забыл про специальное значение (void *)-1.
У меня коллега недавно одну C++ прогу другой версией gcc скомпилял и она стала выражения по другому вычислять. В результате в выводе некоторые строки поменялись местами. Там вроде какие-то перегруженные логические операторы были. И в новой версии кажется отрицание в конце цепочки стало вычисляться раньше остального. Афтар предпологал, видимо, что оно будет считаться всегда лениво или что-то в таком духе.
если речь идет о программах, которые никогда не содержат ошибок - то решения одинаковы: что и с (void*)-1, что и со своим pointer-ом для skip.
если же речь идет о программах, которые могут содержат ошибки (а именно так в реальной жизни и бывает и об адекватном поведении программы во время проявлении ошибки, тогда все становится интереснее. ошибки, навскидку, могут быть следующие:
1. обращение по указателю skip
2. применение free к указателю skip.
при ошибочном обращении по указателю для решения (void*)-1 - на практике, будет segfault, в общем случае - хз что,
для решения со своим указателем - и на практике и в общем случае программа отработает, как ни в чем не бывало (для одних ситуаций - это хорошо, для других не очень).
при ошибочном вызове free: для первого решения - на практике будет segfault, в общем случае - хз что,
для второго решения - на практике и в общем случае будет хз что.
исходя из этого получается, что надежнее использовать решение (void*)-1 (хотя оно мне и меньше нравится)
еще одним контекстом может быть: код пишется одним разработчиком и гарантированно не собирается работать с другим кодом,
или с кодом будут работать разные разработчики и код будет работать с другим кодом.
в первом случаи, опять же все едино.
для второго случая - вариант (void*)-1 может неприятно удивлять, при стыковке его с другим кодом или с утилитами. другой код или утилиты могут использовать функцию типа isValidPointer, которая адекватно работает с null-ем и честными указателями, но плохо переваривает указатели вида (void*)-1,
также другие разработчики, другой код или утилиты могут считать, что ненулевой поинтер - это есть хороший указатель, который можно разыменовывать и т.п.
здесь лучше смотрится вариант, когда по skip выделяется реальная память.
итого: пишет для себя, и как любитель - поэтому, имхо, для него лучше вариант с (void*)-1
Сегфолт хорош тем, что возникает там, где произошла неокрректная операция. В бэктрейсе сразу видно, что случилось и где.Ага, особенно хорошо, когда стектрейс с оптимизированного релиза, который прислали клиенты, у которых программа падает один раз в месяц, из-за чего они теряют, скажем, 5-10 тысяч долларов. Моё мнение в том, что надо разрабатывать код так, чтобы сегфолтов не было, и на Си++ этого вполне можно добиться простыми правилами.
Чтобы вычислить какое-то выражение в вотч-листе, тебе нужно помнить, с чем сравнивать. (void *)-1 заметен и в том случае, если ты этого не помнишь.
В дебаггерах, которыми я пользуюсь, не нужно этого помнить, они умеют сравнивать два произвольных указателя. И даже если я соглашусь, что в каких-то экзотических случаях будет удобнее (void*)-1, то повторю, что жертвовать хорошим кодом ради этого не буду.
То есть: сегфолт -> смотрим в дебагере бактрейс -> разыменовали -1 -> а, я забыл про специальное значение (void *)-1.
То есть я через N лет поправил чужой код -> сегфолт -> мне по почте прислали стектрейс с релиза очень ценного клиента -> из-за O3 нихуя не ясно чё произошло, а если и ясно, то разыменовали -1 -> а, я не понял, чё за хуйня?
Ага, особенно хорошо, когда стектрейс с оптимизированного релиза, который прислали клиенты, у которых программа падает один раз в месяц, из-за чего они теряют, скажем, 5-10 тысяч долларов. Моё мнение в том, что надо разрабатывать код так, чтобы сегфолтов не было, и на Си++ этого вполне можно добиться простыми правилами.это уже какое-то другое программирование. и надёжности от c++ там можно добиться только вот такими правилами.
исходя из этого получается, что надежнее использовать решение (void*)-1 (хотя оно мне и меньше нравится)Я совершенно не понял, как ты сделал этот вывод. Например:
test.c:17: warning: passing argument 1 of ‘free’ discards qualifiers from pointer target type
/usr/include/stdlib.h:488: note: expected ‘void *’ but argument is of type ‘const void *’
Кроме того, стоило бы более детально проанализировать качество обоих решений в плане поддержки и развития.
Ага, особенно хорошо, когда стектрейс с оптимизированного релиза, который прислали клиенты, у которых программа падает один раз в месяц, из-за чего они теряют, скажем, 5-10 тысяч долларов.Как я уже писал раньше, в твоем варианте вообще непредсказуемое поведение получается. Вот разыменовал ты указатель, забыв проверить на "особое" значение. Но ты ведь не просто так его разыменовал, ты хотел с ним что-то сделать. Например, добавить вершину в это дерево. И куда ты ее добавишь в этом случае? К твоей фиктивной вершине. А на это никакой код в других частях программы не рассчитан. Вот и получается, что прога что-то делает, да не то, что надо. По мне так уж пусть лучше падает и ничего не делает, чем делает не то, что должна (ну там, например, банковский перевод не туда отправит, или не тот раздел на винчестере отформатирует).
надо разрабатывать код так, чтобы сегфолтов не былоМне казалось, что надо разрабатывать код так, чтобы он работал. А отсутствие сегфолтов тебе этого, увы, не гарантирует.
В дебаггерах, которыми я пользуюсь, не нужно этого помнить, они умеют сравнивать два произвольных указателя.Чтобы добавить в ватч-лист выражение "p == SKIP" тебе нужно, как минимум, помнить про существование твоей глобальной переменной SKIP. Мне казалось мы рассматриваем случай, когда в коде квыряется кто-то, кто его не писал или ты сам через много лет, когда уже все забыл. В этом случае (void *)-1 удобнее, поскольку по нему сразу видно, что это не корректный указатель, а какое-то специальное значение.
мне по почте прислали стектрейс с релиза очень ценного клиентаЭто нынче так модно, отлаживать проги на "очень ценных клиентах"?
А если серьезно, то смотри в начало поста.
бля, ну ты юморист, 10тыс. долларов за сегфолт. а неверный результат или тот же сегфолт, но в вообще не связанном месте кода эти клиенты скидку получают, типа за сложность отладки?
Мне казалось, что надо разрабатывать код так, чтобы он работал. А отсутствие сегфолтов тебе этого, увы, не гарантирует.А присутствие?
бля, ну ты юморист, 10тыс. долларов за сегфолт. а неверный результат или тот же сегфолт, но в вообще не связанном месте кода эти клиенты скидку получают, типа за сложность отладки?Ты знаешь, если система критична к неверным результатам, то её разрабатывают так, чтобы возникновение такого результата не приводило к ошибкам. Тогда получается, что какая-то операция не проходит, но вся программа, в которой могут обрабатываться тысячи других запросов, продолжает работать. Но вообще это одно из многих предположений, почему сегфолты не являются положительным аргументом в этом споре. Да и вообще в любом другом споре странно было бы слышать, что сегфолт - это аргумент "за", а не "против".
Например, добавить вершину в это дерево. И куда ты ее добавишь в этом случае? К твоей фиктивной вершине.Нет, не добавлю. Если мне придётся писать на Си, и в какой-то момент времени придётся учитывать такое поведение, то в мою фиктивную вершину нельзя будет что-то добавлять. Поэтому я не вижу, каким боком сегфолт будет предпочтительнее сообщения об ошибке, когда другие запросы продолжают обрабатываться.
Если тебе так хочется восстанавливаться после сегфолтов - перехватывай их, в чем проблема-то? Это же не SIGKILL. И чинись после этого, прибиванием сглючившего задания и отправлением багрепорта, а не по принципу "программа не упала, что-то делает - ну и ладно, что-нибудь сделает, наверное".
то в мою фиктивную вершину нельзя будет что-то добавлятьТы как этого собрался добиться?
Ты про такую вещь, как assert слышал? В данном случае сегфолт служит ровно для тех же целей.Где-то здесь уже валяется эпический тред про ассерты и всё, что с ними связано.
Если тебе так хочется восстанавливаться после сегфолтов - перехватывай их, в чем проблема-то? Это же не SIGKILL. И чинись после этого, прибиванием сглючившего задания и отправлением багрепорта, а не по принципу "программа не упала, что-то делает - ну и ладно, что-нибудь сделает, наверное".Перехватывать сегфолты в Си можно только с целью сделать дамп и умереть. Это ещё одна прописная истина, о которой я не собираюсь спорить.
про проверки на верность результата вообще смешно: 1. ошибка может быть в коде проверки, 2. порчи памяти хуй исправишь автоматически.
вообще, если выбирать между сегфолтом и порчей памяти, то что выберешь ты?
Ты как этого собрался добиться?Подумай, это несложно. Когда git писали, говорят, даже ООП на Си занимались. На Си тоже можно делать стабильные отказоустойчивые решения, просто у них очень высокая стоимость. Предлагаю дальше не обсуждать сегфолты, ибо уже давно вышли за рамки дискуссии. Я всё равно не смогу принять их как аргумент "за", и считаю, что бесполезно спорить с тем, кто так аргументирует.
ну тогда надо и вместо NULL использовать специально выделеный указатель, станет меньше сегфолтов, епта.Введение Nullable/Optional - один из известных способов безопасного программирования, даже на Java и C#
про проверки на верность результата вообще смешно: 1. ошйбка может быть в коде проверки, 2. порчи памяти хуй исправишь автоматически.
Куда-то ты в ту степь уехал. Это уже не аргументы за или против какого-либо из предложенных решений.
Перехватывать сегфолты в Си можно только с целью сделать дамп и умереть. Это ещё одна прописная истина, о которой я не собираюсь спорить.То бишь нельзя. Но если очень хочется, то можно.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <inttypes.h>
static jmp_buf invalid_ptr;
static void handler_sigsegv(int x __attribute__unused { __builtin_longjmp(invalid_ptr, 1); }
int is_valid_ptr(void *x)
{
uint8_t c;
struct sigaction saved_act, new_act;
int is_ok = 1;
sigaction(SIGSEGV, NULL, &saved_act);
new_act = saved_act;
new_act.sa_flags |= SA_RESETHAND;
new_act.sa_flags &= ~SA_SIGINFO;
new_act.sa_flags |= SA_NODEFER;
new_act.sa_handler = handler_sigsegv;
sigaction(SIGSEGV, &new_act, NULL);
if (__builtin_setjmp(invalid_ptr {
is_ok = 0;
} else {
c = *uint8_t *)x);
}
sigaction(SIGSEGV, &saved_act, NULL);
return is_ok;
}
void try_ptr(void *x)
{
printf("Point %p is %s" "a valid pointer\n", x,
is_valid_ptr(x) ? "" : "not ");
}
int main(void)
{
int x;
int *l = malloc(1); /* 1 is less than sizeof(int) */
try_ptr(&x);
try_ptr&x) + 7);
try_ptr(NULL);
try_ptr(handler_sigsegv);
try_ptrvoid *)-1);
try_ptr&x) - 1);
try_ptr(l);
try_ptr(l - 1);
try_ptr(l + 20000000);
try_ptr(l + 1);
try_ptrvoid *)0eadbeaf);
return 0;
}
Куда-то ты в ту степь уехал. Это уже не аргументы за или против какого-либо из предложенных решений.Мы с тут пытаемся донести до тебя одну очень простую мысль: если программа начала разыменовывать указатель, который она по ее собственной логике не должна была разыменовывать, то в ней есть бага и она уже делает не то, что должна. И от того, что ты сделаешь так, что на этом разыменовании сегфолта не будет, бага никуда волшебным образом не исчезнет и программа не станет вдруг правильной. Просто она (бага) проявится где-то в другом месте и в другое время, в уже не связном с самой багой куске кода, и ловить ее в этом случае будет сложнее.
На Си тоже можно делать стабильные отказоустойчивые решения, просто у них очень высокая стоимость.Очередной лозунг, который, впрочем, ничего конкретного не утверждает, так, общие слова.
Я всё равно не смогу принять их как аргумент "за", и считаю, что бесполезно спорить с тем, кто так аргументирует.И очередная "прописная истина", которая не требует аргументации. "И кто с ней не согласен, тот дурак."
Мы с тут пытаемся донести до тебя одну очень простую мысль: если программа начала разыменовывать указатель, который она по ее собственной логике не должна была разыменовывать, то в ней есть бага и она уже делает не то, что должна.Вы с ним уже давно ушли не в ту степь. Все аргументы высказаны ещё в самом начале треда, а ты уже давно доказываешь, что сегфолты - это хорошо. Я не согласен с этим, но при этом не спорю, что в программе могут быть ошибки.
Возвращаясь к теме, ещё раз повторю, что использовать указатель на несуществующую память для реализации логики программы не принято. Это хак, и использование таких подходов делает код менее читаемым, затрудняет его поддержку и развитие.
Это хак, и использование таких подходов делает код менее читаемым, затрудняет его поддержку и развитие.вот нам ООП преподавали на примере С++. А могли и на примере ассемблера. Парадигма программирования - она прежде всего в умах, и лишь потом в инструментарии. Настоящий программист на фортране пишет программы на фортране на любом языке программирования. Но вся фишка в том, что коммерческого успеха он не добьётся. Коммерческий успех есть там, где есть традиции программирования, где достаточно большое количество взаимозаменяемых программистов, умеющих пользоваться одной и той же технологией и концепцией. И поэтому языки важны не только сами по себе, как совокупность синтаксических фич, но как совокупность семантики языка и традиций программирования на этом языке. Так вот, ненулевые NULL вполне в традиции программирования для си, как тебе это уже показали. И без этих традиция язык становится менее полезным, потому что традиции закрепляют то, что нельзя формализовать синтаксически. Потому тот, кто хочет программировать на си должен уважать эти традиции, эти отрицательные ошибочные пойнтеры, как, например, и списки на макросах и прочие не вполне красивые с точки зрения ООП инструменты, которые си программисты нарабатывали десятилетиями. В чужой язык со своим уставом не ходят. Мне тоже больше нравится С++ и те концепции, что он породил, но и у чистого Си есть своя область применения. И даже больше чем у плюсов, потому что плюсы конкурируют и вытесняются всякими дотнетами, явами и го, а языков уровня Си уже давно не делают новых.
ы про такую вещь, как assert слышал? В данном случае сегфолт служит ровно для тех же целей.Все не так просто
1) Вот вы все не знаете, а в стандарте С очень неспроста чёрным по белому написано, что NULL это действительно указатель в никуда, а вовсе не в нулевой адрес или ещё что-то. Более того, там в очень осторожных выражениях описано то, что (void*)0 == NULL, типа что это не reinterpret_cast<> (извините за плюсы а что-то вроде (double)0, то есть каст с произвольно сложной внутренней логикой, результат которого может отличаться от того что получится, если сделать *int*)ptr) = 0; например, даже при совпадающих размерах инта и пойнтера. В некоторых архитектурах NULL вовсе не состоит из всех нулей. Не, я понимаю, что вероятность того, что ваш код будет запущен на чём-то отличном от x86(-64) довольно мала, но лучше всё-таки не делать хуйни!
2) Кстати про нуллы и ядро — там на самом деле вообще офигительный баг был: gcc полагает, что доступ к нуллу автоматически приводит к сегфолту. Поэтому когда умный и security aware кернел программер пишет
int descriptor = p->descriptor;
if (NULL == p) return E_INVAL;
// do kernel stuff with the descriptor
именно в таком порядке, потому что С99 ещё не имплементировали, то ещё более умный компилятор тупо убирает весь if. Ну, сегфолт же всё равно случится! Однако если заммапить что-нибудь в нулевую страницу, то сегфолт не случается и открывается уязвимость несмотря на то, что в сурцах эта возможность учтена и корректно обрабатывается! Вообще офигенно, по-моему.
3)
На прошлой работе как-то встретил такое в прошивке для контроллера AVR:
if (cmd[1] == RESET) // если принятая команда RESET
{void(*void0;} // что-то из черной магии
Это не чёрная магия, это "call #0". Переход на нулевой адрес. После запуска микропроцессора он исполняет код начиная с нулевого адреса, так что там лежит инициализационный код, который помимо всего прочего инициализирует все регистры включая SP, так что даже неважно что это call, а не jmp. Ну ёбаный стыд же, тупой как дрова кодер смотрит на прошивку для микроконтроллера нифига в этом не рубя и гыгыкает, "чорная магия" типа.
В некоторых архитектурах NULL вовсе не состоит из всех нулей.не верю. иначе memset не работает для этого.
Кстати про нуллы и ядрокакой-то гавнокод ты в пример привёл. gcc вполне прав.
А он и не работает, а чё? http://c-faq.com/null/machexamp.html
> какой-то гавнокод ты в пример привёл. gcc вполне прав.
Вот один из множества примеров проявления бага, с реальным диффом реального кернел кода: http://lkml.org/lkml/2009/7/6/19
А так — говнокод, конечно. Обращение по нуллу производит undefined results, в которых вполне входит магическое исчезновение последующих проверок.
А можно для проспавших (меня) пояснить, почему чувак так написал изначально? На что рассчитывал?
А можно для проспавших (меня) пояснить, почему чувак так написал изначально? На что рассчитывал?Наверное хотел покороче написать.
А он и не работает, а чё? http://c-faq.com/null/machexamp.htmlну это уже из разряда "забавные курьёзы истории эвм"
сегментная адресация уже толком нигде не используется, боюсь даже в дремучем эмбедеде её не осталось.
да и банально проверка на ноль короче и быстрее во всех системах команд.
Вот один из множества примеров проявления бага, с реальным диффом реального кернел кода: http://lkml.org/lkml/2009/7/6/19%C0 так — говнокод, конечно. Обращение по нуллу производит undefined results, в которых вполне входит магическое исчезновение последующих проверок.какбы пофиг на исчезновение проверок, пэйдж фолты ещё никто не отменял.
и подобные баги даже на еррор-путях сейчас вполне успешно вылавливают статическим анализом.
какбы пофиг на исчезновение проверок, пэйдж фолты ещё никто не отменял.
Ёпт, ты ж СОВЕРШЕННО не понял в чём баг!
Что подумал программист: либо там случится пейдж фолт, либо кто-то замапил нулевую страницу и тогда я специально проверю на нулл и верну ошибку. Оба варианта меня вполне устраивают, так как являются корректной реакцией на передачу инвалидного указателя. Главное, чтоб кернел дальше не пытался оттуда считать какие-то важные данные, которые он думает что его, а на самом деле их подсунул юзер в нулевой странице.
Что подумал компилятор: хаха, тупой программист проверяет на нулл _после_ того, как обращается к переменной, у него ж тогда либо пейдж фолт случается, либо она не нулл, уберу-ка я эту бессмысленную проверку!
Что получилось в результате: дыра. Работающий эксплойт: http://lwn.net/Articles/341773/
В таких случаях надо использовать паттерн NullObject - использовать указатель на неизменяемую структуру/объект с нужными дефолтными значениями полей и реализациями методов (для классов). В результате во многих случаях удастся обойтись вообще без сравнения с ним.
ну это уже из разряда "забавные курьёзы истории эвм"Я тут подумал, что можно ведь заюзать (char *)0 + 1, если рассчитывать на всяких извращенцев тоже.
сегментная адресация уже толком нигде не используется, боюсь даже в дремучем эмбедеде её не осталось.
да и банально проверка на ноль короче и быстрее во всех системах команд.
В таких случаях надо использовать паттерн NullObject - использовать указатель на неизменяемую структуру/объект с нужными дефолтными значениями полей и реализациями методов (для классов). В результате во многих случаях удастся обойтись вообще без сравнения с ним.Ну опять.
да, теперь понял. компиллятор из-за нефатального бага открывает дырень.
Я тут подумал, что можно ведь заюзать (char *)0 + 1, если рассчитывать на всяких извращенцев тоже.
Неа. Это тоже undefined behaviour.
Сишный стандарт в этом смысле совершенно удивительный. Он говорит например что поинтер арифметика определена в только пределах одного statically or dynamically allocated unit. То есть одной статической/на стеке структуры, одного массива (структур или того, что тебе вернул один маллок. Плюс поправки на выравнивание (и да, именно поэтому у calloc такая странная сигнатура). Конечно, in this day and age мы как бы привыкли к byte-addressable flat memory, но всё было как минимум интереснее ещё не в столь далёкие времена 8086 real mode, да и сейчас из-за алайнмента опять же.
Что ещё более удивительно, подобная осторожность формулировок позволяет существовать где-то интересному проектецу safe C, где рантайм это самое энфорсит, типа все пойнтеры — tagged и кидают сегфолт если ты лезешь out of allocation range.
Производительность страдает, конечно, но с другой стороны если такое иметь хотя бы в дебаг режиме, уже дико прекрасно было бы. Но только блин тогда, и именно поэтому и для комфортной работы с другими например статическими анализаторами, нужно знать стандарт и писать код в соответствии с ним, без левых нуллов, без закладывания на то, что уж в long long поинтер точно влезет и там с ним можно будет делать обычную арифметику, что NULL на самом деле состоит из одних нулей (хотя как и в случае IEEE 754 для флоатов это уже можно считать де-факто стандартом и так далее.
Неа. Это тоже undefined behaviour.Это понятно. Но в плане разных архитектур может быть "надёжнее", возможно.
Ну нет уже никакого практического смысла в этих изъёбах. Соблюдение их существенно ограничивает программиста но нисколько не увеличивает практическую портируемость программы. Это никак не поможет в портировании кода на современные "странные" процессоры типа каких-нить gpu, dsp или cell. АФАИК в стандарте даже целочисленное переполнение при суммировании не определено, типа были процессоры где только суммирование с насыщением. Если всё это соблюсти получится бессмысленно чудовищный и жутко медленный код.
А интересно, что говорит стандарт насчет таких вот оптимизаций?
да дурная это оптимизация — вот ворнинг в этом случае можно кинуть, но не более.
Сегментная адресация используется в Native Client - там все сегменты у безопасного кода сдвинуты, чтобы он не мог считать или записать данные небезопасной части кода. К сожалению, винда (да и линукс) такого не любит и при любом исключении немедленно прибивает процесс без вызова обработчиков исключений.
Сегмент программы const data кто-то запретил?
Сегмент программы const data кто-то запретил?Я не в курсе на каких архитектурах реально можно поставить read-only флаг на сегмент памяти. И чтобы ещё компилятор const данные именно в такие сегменты разместил (если вообще у него есть полномочия).
Ну и к тому же в чём преимущество?
#include <stdio.h>
void * memcpy(void * destination, const void * source, size_t num)
{
return "CPY MY MEM";
}
int main
{
printf("%s\n", (char*)memcpy("HAXUS", "THE REIMPLEMENTOR", 0;
printf("%s\n", (char*)memcpy("HAXUS", "THE REIMPLEMENTOR", 1;
return 0;
}
$ gcc -dumpversion
3.4.6
$ gcc test.c -Wall -pedantic -ansi
$ ./a.out
HAXUS
CPY MY MEM
пидарасы, что с них взять.
такойже пиздец есть с printf("%s\n", NULL);
он его заменяет на puts и падает, когда glibc printf печатает "(null)\n"
такойже пиздец есть с printf("%s\n", NULL);пидарасы те кто NULL передаёт в printf
он его заменяет на puts и падает, когда glibc printf печатает "(null)\n"
glibc печатает (null) только на линуксе
собери тоже самое под солярисом, получишь сегфолт
#include <stdio.h>
int main
{
printf("%s\n", NULL);
return 0;
}
$ cc test.c -o test
$ ./test
Segmentation Fault (core dumped)
$ cc -V
cc: Sun C 5.10 SunOS_sparc 2009/06/03
usage: cc [ options] files. Use 'cc -flags' for details
$ gcc test.c -o test
$ ./test
Segmentation Fault (core dumped)
$ gcc --version
gcc (GCC) 3.4.6
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc test.c -o test
$ ldd test
libc.so.1 => /lib/libc.so.1
libm.so.2 => /lib/libm.so.2
/platform/SUNW,T5440/lib/libc_psr.so.1
но в общем передавать NULL в *printf - долбоёбство, т.к. не кроссплатформенно
Я_нихуя_не_понял.жпг. Откуда там взялся хаксус?
memcpy — это builtin, так что gcc знает, что memcpy(a, b, 0) == a
Так это банальная бага в компиляторе.
почему? если программист переопределяет стандартную функцию memcpy, то он должен компилировать с -fno-builtin-memcpy
-fno-builtin-bugs было бы круче
если он такой умный то лучше бы он это знал анализируя её код, а не просто знал
вообще откуда у них столько свободного времени чтобы оптимизировать memcpy(0) =) менеджера бы им толкового, он бы нашел что лучше пооптимизировать, эх...
Алсо, анализировать код всё-таки очень сложно.
Алсо, анализировать код всё-таки очень сложно.Да и к тому же она внешне линкуется и анализировать там нечего.
Я не в курсе на каких архитектурах реально можно поставить read-only флаг на сегмент памяти. И чтобы ещё компилятор const данные именно в такие сегменты разместил (если вообще у него есть полномочия).В x86 есть атрибут страниц "только для чтения". В таких страницах по умолчанию хранится код и туда же компиляторы засовывают константы. Никаких полномочий для этого не надо. Преимущество в том, что при попытке изменения сразу полетит исключение (если кто-нибудь не поменял атрибуты страниц конечно) и никто не будет тратить полгода на поиск в каком это месте константа вдруг изменилась.
Ну и к тому же в чём преимущество?
Ну тут всё равно будет вомзожность разыменовать указатель без последствий сразу же.
Ну все равно при записи все грохнется, это отладить все равно на порядки проще.
Ну все равно при записи все грохнется, это отладить все равно на порядки проще.Проще, но вдруг получится так, что его писать не будут, а просто прочитают и воспользуются прочитанным "не по назначению".
Хз, я так понял, что рассматривается сценарий, где читать как раз можно ( там выше писал что-то о разумных значениях по умолчанию). А если и читать нельзя, то под виндой, по крайней мере, можно VirtualAlloc'ом сделать MEM_RESERVE без MEM_COMMIT и сделать так, что и читать нельзя.
В паттерне NullObject разыменовывать можно. Это сделано как раз для того, чтобы уменьшить число проверок на null'ы. У разных null'ов при этом будут разные значения полей в зависимости от их назначения.
Оставить комментарий
tokuchu
Хочу в C себе сделать ещё один указатель "в никуда". Чтобы можно было делать как-нибудь так:if (ptr == NULL) { /* action 1 */ }
else if (ptr == NULL2) { /* action 2 */ }
else { /* use ptr */ }
В общем, чтобы дополнительный флаг не заводить.
Понятно, что можно сделать что-нибудь вроде (void *)1, или взять ссулку на какую-нибудь ненужную переменную или функцию внутри программы, или просто тупо malloc сделать лишний.
Но я подумал, что может какие специальные методики для такого есть? Т.к. если это будет константа, то так лучше. Может ещё какие заморочки есть?