c null pointer

tokuchu

Хочу в C себе сделать ещё один указатель "в никуда". Чтобы можно было делать как-нибудь так:
if (ptr == NULL) { /* action 1 */ }
else if (ptr == NULL2) { /* action 2 */ }
else { /* use ptr */ }
В общем, чтобы дополнительный флаг не заводить.
Понятно, что можно сделать что-нибудь вроде (void *)1, или взять ссулку на какую-нибудь ненужную переменную или функцию внутри программы, или просто тупо malloc сделать лишний.
Но я подумал, что может какие специальные методики для такого есть? Т.к. если это будет константа, то так лучше. Может ещё какие заморочки есть?

sergey_m

Понятно, что можно сделать что-нибудь вроде (void *)1, или взять ссулку на какую-нибудь ненужную переменную или функцию внутри программы, или просто тупо malloc сделать лишний. Но я подумал, что может какие специальные методики для такого есть?
Вроде ты перечислил все специальные методики :)

tokuchu

Вроде ты перечислил все специальные методики :)
Тогда вопрос в том какая из них предпочтительней? :)

Marinavo_0507

Вроде ты перечислил все специальные методики
можно ещё выравнивать данные и использовать младшие биты :)

tokuchu

можно ещё выравнивать данные и использовать младшие биты :)
Ну это может плохо сказаться на переносимости кода и будет зависеть от флагов компилятора. Вот и с (void *)1 у меня тоже подозрения есть, что вдруг где-нибудь такой указатель возможен.

slonishka

у пенартура в осознанном сновидении он возможен
(все отсылки к реальным персонажам суть симулякры)

vall

глянь на include/linux/err.h

Anturag

 
Понятно, что можно сделать что-нибудь вроде (void *)1
Ну и сделай указатель на любое aligned значение от 0 до (PAGE_SIZE - 1) - в этом диапозоне у тебя гарантированно ничего нет и не будет, ибо работа с VMM постраничная. По поводу malloc и прочих ненужных мыслей - ты же не собираешься разыменовывать, верно? :)

Serab

ты же не не собираешься разыменовывать, верно?
вот-вот, в случае (void *)1 еще и контроль за ошибками со стороны системы получается.

Anturag

Верно, только не надо использовать (void *)1, ибо unaligned - по мне так лучше получить штатную ошибку от VMM, чем data abort на коре.

Werdna

можно сделать что-нибудь вроде (void *)1
Больше быднокодинга! Ты можешь круче, мы знаем.
Вместо того, чтобы сделать грамотный и понятный интерфейс, сделай нечто, что обломает лучшие умы будущего.

vall

глянь на 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);
}

tokuchu

от 0 до (PAGE_SIZE - 1) - в этом диапозоне у тебя гарантированно ничего нет и не будет, ибо работа с VMM постраничная
А 0-я страница гарантированно не может быть использована?

tokuchu

для тех кому лень смотреть. в ядре широко используется такая конструкция
Спасибо за то, что запстил, а то я в /usr/include не нашёл файла и чуть было не расстроился, а это в сорцах ядра оказывается.
Действительно испльзуют, прикольно. А что это за ERR pointer? Он вообще на реальную память указывает когда-нибудь? :)
PS. Кстати в ядре всё же с большим успехом можно гарантировать какие страницы не используются. :)

tokuchu

Больше быднокодинга! Ты можешь круче, мы знаем.
Вместо того, чтобы сделать грамотный и понятный интерфейс, сделай нечто, что обломает лучшие умы будущего.
Ну давай подробнее расскажи-то что тебя смущает? :confused:

kokoc88

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

Werdna

Ну давай подробнее расскажи-то что тебя смущает? :confused:
Да я просто прикалывался.
Если говорить по делу, то лучше интерфейсы сделать явными и код передавать как-то ещё.

Anturag

А 0-я страница гарантированно не может быть использована?
Не гарантировано, туда можно отмапить файл или кусок памяти
Бля, это где так можно? В винде что ли?

tokuchu

Если говорить по делу, то лучше интерфейсы сделать явными и код передавать как-то ещё.
Какие интерфейсы? Какой код? :)
Мне просто нужно избавиться от лишнего флага, который будет место занимать лишнее и им менее удобно пользоваться будет. Возможно его проверка ещё и медленнее будет.

yroslavasako

эх, правильно в яве сделали с их списком исключений

kokoc88

Бля, это где так можно? В винде что ли?
В 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;
}

Anturag

Спасибо за информацию, буду знать.

vall

Это такой стандартный способ вернуть errno вместо поинтера — в ядре ошибки всегда отрицательные.
Последней страницы в ядре нет, т.к. часто используется container_of и надо ловить баги при дереференсе поинтеров полученных применением его к NULL. В юзерспейсе этой страницы тоже быть не должно.

vall

С нулевой пэйджой вообще отдельная история. Её запрещают мапить юзерспейсу чтоб защитится от возможных атак через null-pointer dereferrence или call, ибо если в ядре есть такой баг то оно благополучно получит эти данные или пойдёт исполнять тот код в контексте ядра.
К сожалению долбоёбы пишущие и пакетирующие wine во многих дистрибутивах положили к нему sysctl config молча вырубающий эту защиту под предлогом совместимости с уёбищьным виндовым стофтом, в котором чинили баги типа null-pointer dereferrence замапливанием этой самой нулевой пэйджи =)

Oper

Хочу в C себе сделать ещё один указатель "в никуда". Чтобы можно было делать как-нибудь так:
Если слова "хороший стиль программирования" тебе о чем-нибудь говорят, то "ты не должен этого хотеть" (c)
Лучше заведи дополнительную переменную.

vall

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

Oper

А никто и не говорит про "святую веру" и "непогрешимость". Откуда стремление все обобщать? Речь идет о конкретной проблеме.
новую переменную для состояний ошибки заводить ненужно, для этого уже есть errno.
Заметь, топикстартер ничего не говорил про то, что этот NULL2 нужен для отслеживания "состояния ошибки" в программе.
Получается, как в анекдоте:

...
return NULL; // not NULL, really
...

tokuchu

Это такой стандартный способ вернуть errno вместо поинтера — в ядре ошибки всегда отрицательные.
Последней страницы в ядре нет, т.к. часто используется container_of и надо ловить баги при дереференсе поинтеров полученных применением его к NULL. В юзерспейсе этой страницы тоже быть не должно.
Т.е. вместо (void *)1 лучше использовать (void *)-1?

procenkotanya

(void *)-1
лучше позаботиться, чтобы эта -1 знаково расширилась: (void *)-1LL или (void *ptrdiff_t)-1
(что в тех ядерных макросах достигается за счёт того, что err типа long; видимо, на IL32P64 оно не рассчитано)
в записях типа IL32P64 буквы соответствуют типам: int, long — 32-bit, pointer — 64 bit

tokuchu

Если слова "хороший стиль программирования" тебе о чем-нибудь говорят, то "ты не должен этого хотеть" (c)
Лучше заведи дополнительную переменную.
А мне как раз кажется, что если у меня 3 состояния: "память ещё не выделена", "память не надо выделять", "память выделена", то заводить дополнительную переменную (что приведёт к 4 возможным состояниям) — это и есть нехороший стиль. Потом т.к. их будет много, то экономия места в 2 раза уже как минимум оправдывает данный подход. Всё это мне нужно не в том месте, где можно насрать на память, на производительность, зато будет шаблонно.

vall

> А никто и не говорит про "святую веру" и "непогрешимость".
все так говорят.
> Откуда стремление все обобщать?
работа такая...
Речь идет о конкретной проблеме. Заметь, топикстартер ничего не говорил про то, что этот NULL2 нужен для отслеживания "состояния ошибки" в программе.
ну вот всегда так, не разобравшись что конкретно нужно начинают строить всеобщее универсальное решение.

vall

А мне как раз кажется, что если у меня 3 состояния: "память ещё не выделена", "память не надо выделять", "память выделена", то заводить дополнительную переменную (что приведёт к 4 возможным состояниям) — это и есть нехороший стиль. Потом т.к. их будет много, то экономия места в 2 раза уже как минимум оправдывает данный подход. Всё это мне нужно не в том месте, где можно насрать на память, на производительность, зато будет шаблонно.
Для этого мне не нравиться применять non-NULL т.к. при освобождении придётся думать нужно ли освобождать память.
Лучше выделять именно там где становиться понятно что память нужна, иначе оставлять NULL, а free пофиг на NULL.

tokuchu

Для этого мне не нравиться применять non-NULL т.к. при освобождении придётся думать нужно ли освобождать память.
Лучше выделять именно там где становиться понятно что память нужна, иначе оставлять NULL, а free пофиг на NULL.
Освобождаться оно будет по exit :grin:. А моменты нужно и можно разнесены по времени. У меня строится каркас дерева, где сразу будут отсечены заранее ненужные ветви, а потом уже в динамике достраиваются ветви по входным данным там где можно.

Oper

Для этого мне не нравиться применять non-NULL т.к. при освобождении придётся думать нужно ли освобождать память.
Лучше выделять именно там где становиться понятно что память нужна, иначе оставлять NULL, а free пофиг на NULL.
ну вот всегда так, не разобравшись что конкретно нужно начинают строить всеобщее универсальное решение.

Oper

А мне как раз кажется, что если у меня 3 состояния: "память ещё не выделена", "память не надо выделять", "память выделена", то заводить дополнительную переменную (что приведёт к 4 возможным состояниям) — это и есть нехороший стиль. Потом т.к. их будет много, то экономия места в 2 раза уже как минимум оправдывает данный подход. Всё это мне нужно не в том месте, где можно насрать на память, на производительность, зато будет шаблонно.
Как вариант, "память не нужно выделять" - NULL, "память не выделена" - запись в глобальную переменную, например, ту же errno.

tokuchu

Как вариант, "память не нужно выделять" - NULL, "память не выделена" - запись в глобальную переменную, например, ту же errno.
Одну для всех? :crazy: У меня же будет куча таких указателей.

kokoc88

У меня строится каркас дерева, где сразу будут отсечены заранее ненужные ветви, а потом уже в динамике достраиваются ветви по входным данным там где можно.
И для этого ты делаешь специальный NULL? Я согласен с -ом: :crazy:

tokuchu

И для этого ты делаешь специальный NULL? Я согласен с -ом: :crazy:
Какие у тебя предложения?

kokoc88

Какие у тебя предложения?
Не надо извращений типа (void*)-1 и подобных.
Размести полноценную вершину дерева, чтобы указатель показывал на соответствующий ему тип данных.

tokuchu

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

Serab

В порядке бреда: во всяких деревьях и списках бывает удобно в завершающих элементах ставить указатель на себя. Это может еще и упростить код в некоторых местах. Вдруг применится в твоем случае.

tokuchu

В порядке бреда: во всяких деревьях и списках бывает удобно в завершающих элементах ставить указатель на себя. Это может еще и упростить код в некоторых местах. Вдруг применится в твоем случае.
Идея прикольная, но мне кажется, что "p == SKIP_POINTER" всё же нагляднее, чем "p == q". :) Ну и потом при копировании проблем не возникнет, хотя мне сейчас это вроде не нужно.

kokoc88

Ничего не понял. А так у меня указатели на несоответствующие типы будут указывать что ли?
А так у тебя они будут указывать на хер знает что.

tokuchu

А так у тебя они будут указывать на хер знает что.
Почему. У меня есть указатель типа (struct tree *) и у него будет 3 варианта значений: NULL, SKIP, и на аллоцированную память. Где здесь "хер знает что"?

kokoc88

Почему. У меня есть указатель типа (struct tree *) и у него будет 3 варианта значений: NULL, SKIP, и на аллоцированную память. Где здесь "хер знает что"?
SKIP, если это (void*)-1

tokuchu

SKIP, если это (void*)-1
И что в этом плохого? NULL ведь тоже указывает на хер знает что.

kokoc88

И что в этом плохого? NULL ведь тоже указывает на хер знает что.
NULL как раз никуда не указывает.

tokuchu

NULL как раз никуда не указывает.
Это потому что мы так считаем. Но технически он такой же указатель как и все. А я буду заодно считать, что SKIP тоже никуда не указывает.

kokoc88

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

tokuchu

Но лучше всё равно писать нормальный читаемый код без хаков, тем более даже Си это позволяет.
Я уже писал вроде почему я считаю, что этот код будет читаемый и более нормальный. Тем более, что это наружу никуда не экспортируется. Просто внутреннее соглашение с самим собой.
Тем более если проверки обернуть define-ами или функциями, то вообще не будет никакой разницы в читаемости.

tokuchu

В таком случае говорят, что жираф большой, ему виднее.
Я считаю, что ты не привёл никаких весомых аргументов "против". Только какие-то мифические предположения, что код будет нечитаемым, что-то будет некрасиво (непонятно по каким критериям) и т.п.

kokoc88

Я считаю, что ты не привёл никаких весомых аргументов "против". Только какие-то мифические предположения, что код будет нечитаемым, что-то будет некрасиво (непонятно по каким критериям) и т.п.
Просто я как бы привык к тому, что код приходится расширять и поддерживать.
Для меня это уже достаточные причины для того, чтобы писать без использования хаков.
NULL - это общепринятый стандарт для указателя, который никуда не показывает.
Если написать SKIP как (void*)-1, то это будет хаком: не NULL указатель на память, к которой нельзя обращаться.
Если написать SKIP = create_some_node то код будет намного лучше, безопасней и проще для развития: эту память можно читать и передавать в разные функции, можно добавлять какую-то логику и значения по умолчанию, и так далее.

vall

Если написать SKIP = create_some_node то код будет намного лучше, безопасней и проще для развития: эту память можно читать и передавать в разные функции, можно добавлять какую-то логику и значения по умолчанию, и так далее.
безопаснее? не нужно эту память читать. это всё равно что NULL читать :smirk:

tokuchu

эту память можно читать и передавать в разные функции
Да не будет оно наружу выдаваться.
Это внутреннее устройство дерева. Снаружи в него будут только ложить данные и вынимать. Прямой доступ к вершинам не предполагается.
Если и понадобится расширить, то разве только ещё каким-нибудь NULL-ом. В общем польза от добавления лишних сущностей неочевидна даже в будущем, а вот польза от их исключения есть, как я уже говорил — меньше памяти занимает и проще проверки будут в некоторых местах.

kokoc88

безопаснее? не нужно эту память читать. это всё равно что NULL читать
Ты прочитал всё предложение, которое привёл в качестве цитаты?
Потому что там написано, что не всё равно: например, там могут быть данные по умолчанию.

kokoc88

Да не будет оно наружу выдаваться.
Это внутреннее устройство дерева. Снаружи в него будут только ложить данные и вынимать. Прямой доступ к вершинам не предполагается.
Если и понадобится расширить, то разве только ещё каким-нибудь NULL-ом. В общем польза от добавления лишних сущностей неочевидна даже в будущем, а вот польза от их исключения есть, как я уже говорил — меньше памяти занимает и проще проверки будут в некоторых местах.
Я ещё раз говорю: неважно, будет выдаваться наружу или нет.
Код надо писать так, чтобы его можно было расширять и поддерживать.
Иначе я вообще не вижу смысла создавать тему на форуме. Как я уже сказал, жираф большой, и ему виднее.
Предложенное мною решение не занимает больше памяти в зависимости от размера дерева.
И проверки там точно такие же, а то и более безопасные, потому что не нарушают общепринятых соглашений.

tokuchu

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

kokoc88

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

tokuchu

Теперь я не вижу аргументов.
Моё решение вообще-то тоже с константой.
Константа, которая появляется во время выполнения?
Общепринятые соглашения нарушает неправильное значение указателя на память.
Ну я и говорю, что разыменование нарушается, да.
Что действительно является усложнением программы, так это хаки.
Что действительно бессмысленно, так это писать хаки ради неведомой выгоды.
Да это не более хаки, чем заведение дополнительного флага или ненужной области памяти.

Serab

Мне кажется, что все сильно зависит от области. В прикладном ПО лучше писать понятно и классическими методами, пока не доказано, что участок кода критический по быстродействию или памяти (профилировщиком).
Преждевременная оптимизация — корень зла, да.
Но я вот не знаю, что конкретно пишет , может там очевидная ситуация какая-то.

tokuchu

может там очевидная ситуация какая-то.
Да, там очевидно, что в этом месте нужно быстродействие и памятью не стоит разбрасываться лишний раз тоже. Так бы я эту прогу на perl-е написал. :) Вернее даже так, я написал на perl-е, но оказалось очень медленно. Переписал на C, получилось в 100 раз быстрее. :shocked:

kokoc88

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

Я опять не вижу аргументов. Заведение флага или размещение памяти делаются обычными языковыми конструкциями.

tokuchu

Да. бывают константы, которые хранятся в памяти. В Си можно проинициализировать константу один раз.
Но зачем, если можно взять константу ещё во время компиляции. Может даже оптимизация какая будет благодаря этому.
Я опять не вижу аргументов. Заведение флага или размещение памяти делаются обычными языковыми конструкциями.
Приведение типа — тоже обычная языковая конструкция. Какая разница? Ты вот используешь слово "хак", как аргумент, но почему это "хак"?

kokoc88

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

tokuchu

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

kokoc88

И в этом нет ничего плохого.
Что в этом плохого уже написано выше по теме.
Причём разными людьми и различными словами.

tokuchu

Что в этом плохого уже написано выше по теме.
Причём разными людьми и различными словами.
Прости, но я не заметил ничего конкретного.

kokoc88

Прости, но я не заметил ничего конкретного.
Прощаю. Не все могут принимать аргументы других людей.

tokuchu

Не все могут принимать аргументы других людей.
Согласен. :grin:
В общем твоя позиция-то мне ясна, но для меня она неприменима в данном случае. Я пытался объяснить почему. Ну да ладно, полезных советов тут тоже было немало.

salamander

Общепринятые соглашения нарушает неправильное значение указателя на память.
Какие нафиг общепринятые соглашения нарушаются? У тебя shmat (CONFORMING TO: SVr4, POSIX.1-2001.) возвращает (void *)-1 в случае ошибки, и нормальный указатель в случае успешного выполнения.
Так что никаким хаком это не является. Более того, (void *)-1 очень хорошо удовлетворяет поставленной задаче, поскольку:
1) является одним из значения типа переменной
2) разыменование (некорректное) данного указателя приводит к сегфолту сразу же, а не в другом конце программы
3) гарантированно не совпадает с другими валидными значениями переменной
4) существует в любой момент работы прогаммы
5) никогда не меняется и всегда может быть сравнено на равенство

kokoc88

Какие нафиг общепринятые соглашения нарушаются?
Не использовать указатели на несуществующую память для реализации логики программы.

tokuchu

Не использовать указатели на несуществующую память для реализации логики программы.
Это откуда такое?

vall

Не использовать указатели на несуществующую память для реализации логики программы.
а NULL что такое?

salamander

Во-первых, перечитай .
Во-вторых, (void *)-1, так же как и NULL, используются как специальные значения указателя, причем нередко (например, все программы использующие shmat).
В-третьих, попробую развить идею, что (void *)-1 удобнее в отладке и дальнейшем сопровождении программы, чем предложенный тобой метод.
Любая некорректная операция с ним сразу приводит к сегфолту, что особенно хорошо, когда приходится дописывать программу N-лет спустя. В твоем случае (SKIP = create_some_node поведение будет непредсказуемым: скорее всего будишь зацикливаться или, что гораздо хуже, начинать растить дерево из этой фиктивной вершины.
(void *)-1 хорошо заметен в дебагере. В твоем варианте SKIP по внешнему виду не отличим от любого другого указателя.
Когда ты пишешь SKIP = create_some_node ты выбираешь поистине рандомное значение в качестве специального значения указателя.

yroslavasako

всё же это костыль. Но типично-сишный костыль. Будь разговор о яве, я бы рекомендовал просто оформить правильные исключения. А для си - это нормальная практика. Всё - ради производительности, и выдача результата через (void*) (-1) отвечает этой задаче

tokuchu

А для си - это нормальная практика. Всё - ради производительности, и выдача результата через (void*) (-1) отвечает этой задаче
Ну в случае C и возврата из функции это не только ради производительности полезно. Надо же как-то совмещать ошибки и легитимный возврат, если по каким-то причинам errno не подходит. А возврат двух значений будет сделан через ещё больший костыль, чем это. :)

kokoc88

Во-вторых, (void *)-1, так же как и NULL, используются как специальные значения указателя, причем нередко (например, все программы использующие shmat).

Да, (void*)-1 используется, чтобы проверить на ошибку.
Для написания логики хаки использовать не принято.
NULL является стандартным указателем в никуда (документированным в стандарте и используется обычно соответствующим образом, в хорошем коде на него не возлагается никаких специальных функций.
Любая некорректная операция с ним сразу приводит к сегфолту, что особенно хорошо, когда приходится дописывать программу N-лет спустя.
Чем больше хаков, тем тяжелее что-то дописывать и развивать N лет спустя. Если эта, в общем-то, очевидная вещь кому-то не понятна, то я не вижу смысла продолжать дискуссию.

kokoc88

Это откуда такое?
Мне бы было намного интереснее узнать, откуда обратное.

kokoc88

а 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 стандартом языка не описан.

tokuchu

Мне бы было намного интереснее узнать, откуда обратное.
Правило либо есть, либо нет. Чтобы его не было — это не надо придумывать ничего. А ты утверждал, что оно есть, так что тебе доказывать. А "обратного" доказывать не надо.

procenkotanya

Мне бы было намного интереснее узнать, откуда обратное.
Твоё утверждение:
Не использовать указатели на несуществующую память для реализации логики программы.
не будучи как-то обосновано, пока что похоже на какую-то догму, а не рациональную рекомендацию. Кстати, что скажешь насчёт std::vector<T>::end он же тоже указывает на несуществующую память?

tokuchu

Правда это из стандарта плюсов, но всё равно даёт право говорить о том, что это общепринятая вещь. Указатель (void*)-1 стандартом языка не описан.
А ты в своей программе реализовал класс. Он же тоже не общепринятый изначально и в стандарте не описан. :)
Так же и (void *)-1 в каждой программе не нужен. Зачем его описывать в стандарте?

salamander

Чем больше хаков, тем тяжелее что-то дописывать и развивать N лет спустя. Если эта, в общем-то, очевидная вещь кому-то не понятна, то я не вижу смысла продолжать дискуссию.
Опять какие-то лозунги. Ты два моих предыдущих поста до конца дочитал? Я там как раз объяснял почему в данном случае решение с (void *)-1 с точки зрения сопровождения и развития программы в дальнейшем лучше.

kokoc88

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

kokoc88

Ты два моих предыдущих поста до конца дочитал? Я там как раз объяснял почему в данном случае решение с (void *)-1 с точки зрения сопровождения и развития программы в дальнейшем лучше.
Дочитал. Но не понял, почему сегфолт чем-то особенно хорош через N лет. А если совсем особенно дописать код так, что сегфолты будут только у клиентов раз в месяц...
Что касается дебагеров, оба моих дебагера способны в вотч листе вычислять выражения типа true/false, так что не знаю. Может быть, было бы полезно в каких-то случаях, но впиливать хаки в код ради этого я бы не стал.
Гарантированного несовпадения с (void*)-1 может и не быть, особенно при кроссплатформенной разработке.

kokoc88

Правило либо есть, либо нет. Чтобы его не было — это не надо придумывать ничего. А ты утверждал, что оно есть, так что тебе доказывать. А "обратного" доказывать не надо.
Доказывать, что использовать указатель на несуществующую память плохо - не надо, потому что такие указатели описаны в стандарте со словами "undefined behavior". А вот если кто-то говорит, что можно и это безопасно - тогда пусть потрудится обосновать.

tokuchu

потому что такие указатели описаны в стандарте со словами "undefined behavior".
Дай ссылку где там написано, что _сравнение_ таких указателей "undefined behavior".

tokuchu

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

Serab

Undefined behaviour иногда очень undefined. Я один раз переписывал программку из Рихтера, которая работает с реестром, на unicode. И где-то забыл-таки правильной функцией воспользоваться. Так оно мне снесло кучу ключей из CLSID, я прифигел, ни один exe'шник запустить из проводника нельзя было. В итоге уже не помню, как выкрутился, взял сохранение реестра у коллеги и как-то применил его. так что уж лучше segfault'ы :)

salamander

Дочитал. Но не понял, почему сегфолт чем-то особенно хорош через N лет. А если совсем особенно дописать код так, что сегфолты будут только у клиентов раз в месяц...
Что касается дебагеров, оба моих дебагера способны в вотч листе вычислять выражения типа true/false, так что не знаю. Может быть, было бы полезно в каких-то случаях, но впиливать хаки в код ради этого я бы не стал.
Гарантированного несовпадения с (void*)-1 может и не быть, особенно при кроссплатформенной разработке.
Сегфолт хорош тем, что возникает там, где произошла неокрректная операция. В бэктрейсе сразу видно, что случилось и где.
Чтобы вычислить какое-то выражение в вотч-листе, тебе нужно помнить, с чем сравнивать. (void *)-1 заметен и в том случае, если ты этого не помнишь.
То есть: сегфолт -> смотрим в дебагере бактрейс -> разыменовали -1 -> а, я забыл про специальное значение (void *)-1.

tokuchu

У меня коллега недавно одну C++ прогу другой версией gcc скомпилял и она стала выражения по другому вычислять. В результате в выводе некоторые строки поменялись местами. :grin: Там вроде какие-то перегруженные логические операторы были. И в новой версии кажется отрицание в конце цепочки стало вычисляться раньше остального. Афтар предпологал, видимо, что оно будет считаться всегда лениво или что-то в таком духе.

Dasar

имхо, стоит сначала договориться о контексте дискуссии.
если речь идет о программах, которые никогда не содержат ошибок - то решения одинаковы: что и с (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

kokoc88

Сегфолт хорош тем, что возникает там, где произошла неокрректная операция. В бэктрейсе сразу видно, что случилось и где.
Ага, особенно хорошо, когда стектрейс с оптимизированного релиза, который прислали клиенты, у которых программа падает один раз в месяц, из-за чего они теряют, скажем, 5-10 тысяч долларов. Моё мнение в том, что надо разрабатывать код так, чтобы сегфолтов не было, и на Си++ этого вполне можно добиться простыми правилами.
Чтобы вычислить какое-то выражение в вотч-листе, тебе нужно помнить, с чем сравнивать. (void *)-1 заметен и в том случае, если ты этого не помнишь.

В дебаггерах, которыми я пользуюсь, не нужно этого помнить, они умеют сравнивать два произвольных указателя. И даже если я соглашусь, что в каких-то экзотических случаях будет удобнее (void*)-1, то повторю, что жертвовать хорошим кодом ради этого не буду.
То есть: сегфолт -> смотрим в дебагере бактрейс -> разыменовали -1 -> а, я забыл про специальное значение (void *)-1.

То есть я через N лет поправил чужой код -> сегфолт -> мне по почте прислали стектрейс с релиза очень ценного клиента -> из-за O3 нихуя не ясно чё произошло, а если и ясно, то разыменовали -1 -> а, я не понял, чё за хуйня?

vall

Ага, особенно хорошо, когда стектрейс с оптимизированного релиза, который прислали клиенты, у которых программа падает один раз в месяц, из-за чего они теряют, скажем, 5-10 тысяч долларов. Моё мнение в том, что надо разрабатывать код так, чтобы сегфолтов не было, и на Си++ этого вполне можно добиться простыми правилами.
это уже какое-то другое программирование. и надёжности от c++ там можно добиться только вот такими правилами.

kokoc88

исходя из этого получается, что надежнее использовать решение (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 *’

Кроме того, стоило бы более детально проанализировать качество обоих решений в плане поддержки и развития.

salamander

Ага, особенно хорошо, когда стектрейс с оптимизированного релиза, который прислали клиенты, у которых программа падает один раз в месяц, из-за чего они теряют, скажем, 5-10 тысяч долларов.
Как я уже писал раньше, в твоем варианте вообще непредсказуемое поведение получается. Вот разыменовал ты указатель, забыв проверить на "особое" значение. Но ты ведь не просто так его разыменовал, ты хотел с ним что-то сделать. Например, добавить вершину в это дерево. И куда ты ее добавишь в этом случае? К твоей фиктивной вершине. А на это никакой код в других частях программы не рассчитан. Вот и получается, что прога что-то делает, да не то, что надо. По мне так уж пусть лучше падает и ничего не делает, чем делает не то, что должна (ну там, например, банковский перевод не туда отправит, или не тот раздел на винчестере отформатирует).
надо разрабатывать код так, чтобы сегфолтов не было
Мне казалось, что надо разрабатывать код так, чтобы он работал. А отсутствие сегфолтов тебе этого, увы, не гарантирует.
В дебаггерах, которыми я пользуюсь, не нужно этого помнить, они умеют сравнивать два произвольных указателя.
Чтобы добавить в ватч-лист выражение "p == SKIP" тебе нужно, как минимум, помнить про существование твоей глобальной переменной SKIP. Мне казалось мы рассматриваем случай, когда в коде квыряется кто-то, кто его не писал или ты сам через много лет, когда уже все забыл. В этом случае (void *)-1 удобнее, поскольку по нему сразу видно, что это не корректный указатель, а какое-то специальное значение.
мне по почте прислали стектрейс с релиза очень ценного клиента
Это нынче так модно, отлаживать проги на "очень ценных клиентах"?
А если серьезно, то смотри в начало поста.

Serab

бля, ну ты юморист, 10тыс. долларов за сегфолт. а неверный результат или тот же сегфолт, но в вообще не связанном месте кода эти клиенты скидку получают, типа за сложность отладки?

kokoc88

Мне казалось, что надо разрабатывать код так, чтобы он работал. А отсутствие сегфолтов тебе этого, увы, не гарантирует.
А присутствие?

kokoc88

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

kokoc88

Например, добавить вершину в это дерево. И куда ты ее добавишь в этом случае? К твоей фиктивной вершине.
Нет, не добавлю. Если мне придётся писать на Си, и в какой-то момент времени придётся учитывать такое поведение, то в мою фиктивную вершину нельзя будет что-то добавлять. Поэтому я не вижу, каким боком сегфолт будет предпочтительнее сообщения об ошибке, когда другие запросы продолжают обрабатываться.

salamander

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

salamander

то в мою фиктивную вершину нельзя будет что-то добавлять
Ты как этого собрался добиться?

kokoc88

Ты про такую вещь, как assert слышал? В данном случае сегфолт служит ровно для тех же целей.
Где-то здесь уже валяется эпический тред про ассерты и всё, что с ними связано.
Если тебе так хочется восстанавливаться после сегфолтов - перехватывай их, в чем проблема-то? Это же не SIGKILL. И чинись после этого, прибиванием сглючившего задания и отправлением багрепорта, а не по принципу "программа не упала, что-то делает - ну и ладно, что-нибудь сделает, наверное".
Перехватывать сегфолты в Си можно только с целью сделать дамп и умереть. Это ещё одна прописная истина, о которой я не собираюсь спорить.

Serab

ну тогда надо и вместо NULL использовать специально выделеный указатель, станет меньше сегфолтов, епта.
про проверки на верность результата вообще смешно: 1. ошибка может быть в коде проверки, 2. порчи памяти хуй исправишь автоматически.
вообще, если выбирать между сегфолтом и порчей памяти, то что выберешь ты?

kokoc88

Ты как этого собрался добиться?
Подумай, это несложно. Когда git писали, говорят, даже ООП на Си занимались. На Си тоже можно делать стабильные отказоустойчивые решения, просто у них очень высокая стоимость. Предлагаю дальше не обсуждать сегфолты, ибо уже давно вышли за рамки дискуссии. Я всё равно не смогу принять их как аргумент "за", и считаю, что бесполезно спорить с тем, кто так аргументирует.

kokoc88

ну тогда надо и вместо NULL использовать специально выделеный указатель, станет меньше сегфолтов, епта.
Введение Nullable/Optional - один из известных способов безопасного программирования, даже на Java и C#
про проверки на верность результата вообще смешно: 1. ошйбка может быть в коде проверки, 2. порчи памяти хуй исправишь автоматически.

Куда-то ты в ту степь уехал. Это уже не аргументы за или против какого-либо из предложенных решений.

salamander

Перехватывать сегфолты в Си можно только с целью сделать дамп и умереть. Это ещё одна прописная истина, о которой я не собираюсь спорить.
То бишь нельзя. Но если очень хочется, то можно.
#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;
}

salamander

Куда-то ты в ту степь уехал. Это уже не аргументы за или против какого-либо из предложенных решений.
Мы с тут пытаемся донести до тебя одну очень простую мысль: если программа начала разыменовывать указатель, который она по ее собственной логике не должна была разыменовывать, то в ней есть бага и она уже делает не то, что должна. И от того, что ты сделаешь так, что на этом разыменовании сегфолта не будет, бага никуда волшебным образом не исчезнет и программа не станет вдруг правильной. Просто она (бага) проявится где-то в другом месте и в другое время, в уже не связном с самой багой куске кода, и ловить ее в этом случае будет сложнее.

salamander

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

kokoc88

Мы с тут пытаемся донести до тебя одну очень простую мысль: если программа начала разыменовывать указатель, который она по ее собственной логике не должна была разыменовывать, то в ней есть бага и она уже делает не то, что должна.
Вы с ним уже давно ушли не в ту степь. Все аргументы высказаны ещё в самом начале треда, а ты уже давно доказываешь, что сегфолты - это хорошо. Я не согласен с этим, но при этом не спорю, что в программе могут быть ошибки.
Возвращаясь к теме, ещё раз повторю, что использовать указатель на несуществующую память для реализации логики программы не принято. Это хак, и использование таких подходов делает код менее читаемым, затрудняет его поддержку и развитие.

yroslavasako

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

Serab

ы про такую вещь, как assert слышал? В данном случае сегфолт служит ровно для тех же целей.
Все не так просто :grin:

bleyman

Занудности псто.
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. Ну ёбаный стыд же, тупой как дрова кодер смотрит на прошивку для микроконтроллера нифига в этом не рубя и гыгыкает, "чорная магия" типа.

vall

В некоторых архитектурах NULL вовсе не состоит из всех нулей.
не верю. иначе memset не работает для этого.
Кстати про нуллы и ядро
какой-то гавнокод ты в пример привёл. gcc вполне прав.

bleyman

> не верю. иначе memset не работает для этого.
А он и не работает, а чё? http://c-faq.com/null/machexamp.html
> какой-то гавнокод ты в пример привёл. gcc вполне прав.
Вот один из множества примеров проявления бага, с реальным диффом реального кернел кода: http://lkml.org/lkml/2009/7/6/19
А так — говнокод, конечно. Обращение по нуллу производит undefined results, в которых вполне входит магическое исчезновение последующих проверок.

Serab

А можно для проспавших (меня) пояснить, почему чувак так написал изначально? На что рассчитывал?

tokuchu

А можно для проспавших (меня) пояснить, почему чувак так написал изначально? На что рассчитывал?
Наверное хотел покороче написать.

vall

А он и не работает, а чё? http://c-faq.com/null/machexamp.html
ну это уже из разряда "забавные курьёзы истории эвм"
сегментная адресация уже толком нигде не используется, боюсь даже в дремучем эмбедеде её не осталось.
да и банально проверка на ноль короче и быстрее во всех системах команд.
Вот один из множества примеров проявления бага, с реальным диффом реального кернел кода: http://lkml.org/lkml/2009/7/6/19%C0 так — говнокод, конечно. Обращение по нуллу производит undefined results, в которых вполне входит магическое исчезновение последующих проверок.
какбы пофиг на исчезновение проверок, пэйдж фолты ещё никто не отменял.
и подобные баги даже на еррор-путях сейчас вполне успешно вылавливают статическим анализом.

bleyman

какбы пофиг на исчезновение проверок, пэйдж фолты ещё никто не отменял.

Ёпт, ты ж СОВЕРШЕННО не понял в чём баг!
Что подумал программист: либо там случится пейдж фолт, либо кто-то замапил нулевую страницу и тогда я специально проверю на нулл и верну ошибку. Оба варианта меня вполне устраивают, так как являются корректной реакцией на передачу инвалидного указателя. Главное, чтоб кернел дальше не пытался оттуда считать какие-то важные данные, которые он думает что его, а на самом деле их подсунул юзер в нулевой странице.
Что подумал компилятор: хаха, тупой программист проверяет на нулл _после_ того, как обращается к переменной, у него ж тогда либо пейдж фолт случается, либо она не нулл, уберу-ка я эту бессмысленную проверку!
Что получилось в результате: дыра. Работающий эксплойт: http://lwn.net/Articles/341773/

SPARTAK3959

В таких случаях надо использовать паттерн NullObject - использовать указатель на неизменяемую структуру/объект с нужными дефолтными значениями полей и реализациями методов (для классов). В результате во многих случаях удастся обойтись вообще без сравнения с ним.

tokuchu

ну это уже из разряда "забавные курьёзы истории эвм"
сегментная адресация уже толком нигде не используется, боюсь даже в дремучем эмбедеде её не осталось.
да и банально проверка на ноль короче и быстрее во всех системах команд.
Я тут подумал, что можно ведь заюзать (char *)0 + 1, если рассчитывать на всяких извращенцев тоже. :)

tokuchu

В таких случаях надо использовать паттерн NullObject - использовать указатель на неизменяемую структуру/объект с нужными дефолтными значениями полей и реализациями методов (для классов). В результате во многих случаях удастся обойтись вообще без сравнения с ним.
Ну опять. :)

vall

да, теперь понял. компиллятор из-за нефатального бага открывает дырень.

bleyman

Я тут подумал, что можно ведь заюзать (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 для флоатов это уже можно считать де-факто стандартом и так далее.

tokuchu

Неа. Это тоже undefined behaviour.
Это понятно. Но в плане разных архитектур может быть "надёжнее", возможно.

vall

Ну нет уже никакого практического смысла в этих изъёбах. Соблюдение их существенно ограничивает программиста но нисколько не увеличивает практическую портируемость программы. Это никак не поможет в портировании кода на современные "странные" процессоры типа каких-нить gpu, dsp или cell. АФАИК в стандарте даже целочисленное переполнение при суммировании не определено, типа были процессоры где только суммирование с насыщением. Если всё это соблюсти получится бессмысленно чудовищный и жутко медленный код.

apl13

А интересно, что говорит стандарт насчет таких вот оптимизаций?

vall

да дурная это оптимизация — вот ворнинг в этом случае можно кинуть, но не более.

SPARTAK3959

Сегментная адресация используется в Native Client - там все сегменты у безопасного кода сдвинуты, чтобы он не мог считать или записать данные небезопасной части кода. К сожалению, винда (да и линукс) такого не любит и при любом исключении немедленно прибивает процесс без вызова обработчиков исключений.

SPARTAK3959

Сегмент программы const data кто-то запретил?

tokuchu

Сегмент программы const data кто-то запретил?
Я не в курсе на каких архитектурах реально можно поставить read-only флаг на сегмент памяти. И чтобы ещё компилятор const данные именно в такие сегменты разместил (если вообще у него есть полномочия).
Ну и к тому же в чём преимущество?

bleyman

Кстати ещё про оптимизации — инджой:

#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

vall

4.4.3 аналогично.
пидарасы, что с них взять.
такойже пиздец есть с printf("%s\n", NULL);
он его заменяет на puts и падает, когда glibc printf печатает "(null)\n"

ava3443

такойже пиздец есть с printf("%s\n", NULL);
он его заменяет на puts и падает, когда glibc printf печатает "(null)\n"
пидарасы те кто NULL передаёт в printf
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.

ava3443

вообще гоню я на glibc: чё-то не вижу glibc на солярисе :)
$ 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 - долбоёбство, т.к. не кроссплатформенно

apl13

Я_нихуя_не_понял.жпг. Откуда там взялся хаксус?

procenkotanya

memcpy — это builtin, так что gcc знает, что memcpy(a, b, 0) == a

apl13

Так это банальная бага в компиляторе.

procenkotanya

почему? если программист переопределяет стандартную функцию memcpy, то он должен компилировать с -fno-builtin-memcpy

vall

-fno-builtin-bugs было бы круче :grin:

rosali

> gcc знает, что memcpy(a, b, 0) == a
если он такой умный то лучше бы он это знал анализируя её код, а не просто знал ;)
вообще откуда у них столько свободного времени чтобы оптимизировать memcpy(0) =) менеджера бы им толкового, он бы нашел что лучше пооптимизировать, эх...

bleyman

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

tokuchu

Алсо, анализировать код всё-таки очень сложно.
Да и к тому же она внешне линкуется и анализировать там нечего. :)

SPARTAK3959

Я не в курсе на каких архитектурах реально можно поставить read-only флаг на сегмент памяти. И чтобы ещё компилятор const данные именно в такие сегменты разместил (если вообще у него есть полномочия).
Ну и к тому же в чём преимущество?
В x86 есть атрибут страниц "только для чтения". В таких страницах по умолчанию хранится код и туда же компиляторы засовывают константы. Никаких полномочий для этого не надо. Преимущество в том, что при попытке изменения сразу полетит исключение (если кто-нибудь не поменял атрибуты страниц конечно) и никто не будет тратить полгода на поиск в каком это месте константа вдруг изменилась.

tokuchu

Ну тут всё равно будет вомзожность разыменовать указатель без последствий сразу же.

Serab

Ну все равно при записи все грохнется, это отладить все равно на порядки проще.

tokuchu

Ну все равно при записи все грохнется, это отладить все равно на порядки проще.
Проще, но вдруг получится так, что его писать не будут, а просто прочитают и воспользуются прочитанным "не по назначению".

Serab

Хз, я так понял, что рассматривается сценарий, где читать как раз можно ( там выше писал что-то о разумных значениях по умолчанию). А если и читать нельзя, то под виндой, по крайней мере, можно VirtualAlloc'ом сделать MEM_RESERVE без MEM_COMMIT и сделать так, что и читать нельзя.

SPARTAK3959

В паттерне NullObject разыменовывать можно. Это сделано как раз для того, чтобы уменьшить число проверок на null'ы. У разных null'ов при этом будут разные значения полей в зависимости от их назначения.
Оставить комментарий
Имя или ник:
Комментарий: