2014 dxdy logo

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

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




Начать новую тему Ответить на тему На страницу Пред.  1, 2, 3  След.
 
 
Сообщение13.01.2008, 21:14 


21/03/06
1545
Москва
незваный гость писал(а):
e2e4 писал(а):
А еще я встречал в одной компании следующий тест: определить, в какую сторону (уменьшается или увеличивается) изменяется адрес верхушки стека в некоторой неизвестной архитектуре, на которой будет запущена тестовая программа.

А это возможно (100% надёжно)? Есть ведь масса нюансов — например, знаковые или беззнаковые указатели, с каким типом совместимы указатели, и т.п.

Ну во-первых, этот тест предложил не я, я просто слышал о таком.
А во-вторых, навскидку вроде бы не вижу проблем - вызываем функцию, принимающую два параметра типа int, вспоминаем, какой параметр - первый или второй кладется на верхушку стека (я лично не помню :) ), сравниваем адреса переменных, хранящих параметры ф-ии. В большинстве случаев получим рост стека сверху-вниз.
Кстати, насколько я помню стандарт, указатель вроде-бы должен умещаться в тип int, точнее unsigned int, так что тут мудрить особо нечего. Указатели типа far - это нестандартные, непереносимые расширения языка.
Даже если это не так, то размер int должен соответствовать разрядности АЛУ, разрядность адресуемого пр-ва бывает больше, но это все - пережитки прошлого имхо.
В общем, если человек считает, что размер указателя равен размеру int - это уже неплохо.

незваный гость писал(а):
Под рамочной структурой я в данном случае понимаю то, что нормой для тела цикла, ветки оператора ветвления является более, чем одно предложение. с этой точки зрения синтаксис, предлагающий группировать гораздо удобнее. Сравните:
...
Во втором случае эта малозаметная ошибка просто невозможна. И опять, while-do и do-end образуют рамки.
...
e2e4 писал(а):

Чем плохи фигурные скобки?

(1) Тем, что их можно не писать и (2) тем, что они визуально засоряют программу.

Ну тут все-таки спор о нравится/ненравится. Мы решили не спорить на эту тему, замечу лишь 2 объективных обстоятельства:
1. Скобки - это один символ -> писать их легче, чем слово;
2. Как Вы словами опишите следующий специфический код:
Код:
int a = 10;
{
  int a;
  a = 5;
  printf("%i",a);
}
printf("%i",a);

т.е. скобки - это все-таки элемент выделения блока, а не просто замена ключевых слов.

незваный гость писал(а):
Я могу ещё вспомнить Clarion: там в качестве заключителя использовалась точка, и принято было писать так:...

Ну свосем не понравилось многоточие в Clarion, уж простите :).

Добавлено спустя 55 секунд:

Вообще-то скобки, ключевые слова - это все дело привычки... Вот языки типа forth - "совсем другое дело" (C) :)

 Профиль  
                  
 
 
Сообщение13.01.2008, 23:08 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
e2e4 писал(а):
вспоминаем, какой параметр - первый или второй кладется на верхушку стека (я лично не помню)

Вообще говоря, зависит от реализации :)

e2e4 писал(а):
сравниваем адреса переменных, хранящих параметры ф-ии

Интересно, как? Операция сравнения, по моему, не определена для указателей. А, как я уже напоминал, перевод в целый тип зависит от архитектуры и от компилятора.

e2e4 писал(а):
Кстати, насколько я помню стандарт, указатель вроде-бы должен умещаться в тип int, точнее unsigned int, так что тут мудрить особо нечего. Указатели типа far - это нестандартные, непереносимые расширения языка.

Указатели типа far, конечно, устарели. А вот 64-битное адресное пространство — нет. Делать же каждое целое 64-битным — дурь, и пустой расход всех ресурсов. Вот и делается 32 битный int, 64 битный pointer и long int. Некоторые, впрочем, идут ещё дальше (но это уже по памяти, давно не сталкивался): 32 битный int и long int, 64 битный pointer и long long int.

Кстати, о переносимости программы на С: стандарт гарантирует совместимость с некоторым целым типом. Хоть бы #define для этого типа гарантировали.



А функцию я бы писал иначе:
Код:
#define INTPTR unsigned long int /* it depends!!! */
#if sizeof(int*) != sizeof(INTPTR)
#  error INTPTR type selected incorrectly
#endif

char* foo(int* v) {
  return !v? foo(&v): (INTPTR)v < (INTPTR)&v? "up": "down";
}

int bar() {
  printf("%s\n", foo(NULL));
}
При этом подходе мы привязываемся именно к росту стека, а не к логике расположения параметров на стеке. Очень надеюсь, Вам понравится стиль. :) В принципе, можно было бы поколдовать и с длиной ptr (при помощи препроцессора), и со знаком (потребовалось бы не два, а три вызова — ну и хрен с ними) — если нужен действительно переносимый код. Беда в том, что в некоторых компиляторах long long int вызовет ошибку времени компиляции.

e2e4 писал(а):
1. Скобки - это один символ -> писать их легче, чем слово;

Меня редко интересует писать. Меня интересует читать, и не делать ошибки.

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

e2e4 писал(а):
т.е. скобки - это все-таки элемент выделения блока, а не просто замена ключевых слов.

И часто Вы пишете именно блоки? Я их встречал исключительно редко.

Обычно языки предоставляют возможность написать блок.

e2e4 писал(а):
Ну свосем не понравилось многоточие в Clarion, уж простите

Мне тоже. Я помянул его для демонстрации многообразия мира…

e2e4 писал(а):
Вообще-то скобки, ключевые слова - это все дело привычки

Слышал я этот аргумент. Есть пословица — к хорошему привыкаешь быстро. А вот к {} я не смог привыкнуть за много лет. Видимо, для меня рамочные языки лучше — к Python я привык за час. :)

 Профиль  
                  
 
 
Сообщение15.01.2008, 02:56 


21/03/06
1545
Москва
незваный гость писал(а):
:evil:
e2e4 писал(а):
вспоминаем, какой параметр - первый или второй кладется на верхушку стека (я лично не помню)

Вообще говоря, зависит от реализации :)

Извините, существуют стандартное понятие "C-calls", "Pascal-calls" и т.д. Если бы порядок параметров зависел от реализации компиляторов, фигня бы получилась при попытке написания программы, собираемой из объектников, скомпилированных с разных языков.

незваный гость писал(а):
e2e4 писал(а):
сравниваем адреса переменных, хранящих параметры ф-ии

Интересно, как? Операция сравнения, по моему, не определена для указателей. А, как я уже напоминал, перевод в целый тип зависит от архитектуры и от компилятора.

Да, тут Вы совершенно правы. Я как-то упустил из вида операцию приведения типа указателя к целому.

незваный гость писал(а):
Кстати, о переносимости программы на С: стандарт гарантирует совместимость с некоторым целым типом. Хоть бы #define для этого типа гарантировали.

Почти на 100% можно быть уверенным, что разрядность int соответствует разрядности АЛУ процессора. А совсем твердая гарантия - это sizeof(char) = 1. Хотя это мало помогает.

незваный гость писал(а):
e2e4 писал(а):
1. Скобки - это один символ -> писать их легче, чем слово;

Меня редко интересует писать. Меня интересует читать, и не делать ошибки.

Практически был уверен, что Вы так и напишите :). В общем-то, согласен.

незваный гость писал(а):
e2e4 писал(а):
т.е. скобки - это все-таки элемент выделения блока, а не просто замена ключевых слов.

И часто Вы пишете именно блоки? Я их встречал исключительно редко.

А вот тут я сиронизирую: я блоки не пишу, я ими думаю :).

незваный гость писал(а):
А функцию я бы писал иначе:
Код:
#define INTPTR unsigned long int /* it depends!!! */
#if sizeof(int*) != sizeof(INTPTR)
#  error INTPTR type selected incorrectly
#endif

char* foo(int* v) {
  return !v? foo(&v): (INTPTR)v < (INTPTR)&v? "up": "down";
}

int bar() {
  printf("%s\n", foo(NULL));
}
При этом подходе мы привязываемся именно к росту стека, а не к логике расположения параметров на стеке. Очень надеюсь, Вам понравится стиль. :) В принципе, можно было бы поколдовать и с длиной ptr (при помощи препроцессора), и со знаком (потребовалось бы не два, а три вызова — ну и хрен с ними) — если нужен действительно переносимый код. Беда в том, что в некоторых компиляторах long long int вызовет ошибку времени компиляции.

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

Добавлено спустя 1 час 15 минут 25 секунд:

e2e4 писал(а):
незваный гость писал(а):
e2e4 писал(а):
сравниваем адреса переменных, хранящих параметры ф-ии

Интересно, как? Операция сравнения, по моему, не определена для указателей. А, как я уже напоминал, перевод в целый тип зависит от архитектуры и от компилятора.

Да, тут Вы совершенно правы. Я как-то упустил из вида операцию приведения типа указателя к целому.

Хотя нет, только что нормально скомпилировал и получил правильный результат на Bulder'е следующего кода:

Код:
int a[10];
int *p1,*p2;
p1 = &a[0];
p2 = &a[1];
Label1->Caption=AnsiString(p1-p2);

Т.е. с указателями определены операции сложения/вычитания без явного их приведения к целому типу. Результат операции - целое число. Соответственно, вычитая один указатель из другого, и сравнивая с нулем уже можно сказать, какой указатель больше, без принудительного приведения к целому.

e2e4 писал(а):
незваный гость писал(а):
Кстати, о переносимости программы на С: стандарт гарантирует совместимость с некоторым целым типом. Хоть бы #define для этого типа гарантировали.

Почти на 100% можно быть уверенным, что разрядность int соответствует разрядности АЛУ процессора. А совсем твердая гарантия - это sizeof(char) = 1. Хотя это мало помогает.

Да, и как правило разрядность char - минимально адресуемое кол-во бит ОЗУ (машинное слово).

 Профиль  
                  
 
 
Сообщение15.01.2008, 03:09 
Заслуженный участник


31/12/05
1407
e2e4 писал(а):
Хотя нет, только что нормально скомпилировал и получил правильный результат на Bulder'е следующего кода:
Код:
int a[10];
int *p1,*p2;
p1 = &a[0];
p2 = &a[1];
Label1->Caption=AnsiString(p1-p2);

Т.е. с указателями определены операции сложения/вычитания без явного их приведения к целому типу. Результат операции - целое число.
Согласно стандарту это допустимо только для указателей на элементы одного массива:
Цитата:
When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; the result is the difference of the subscripts of the two array elements.
В начале 90-х Borland сделал связанную с этим ошибку в Turbo Vision: в библиотеке потоков они сравнивали друг с другом на меньше-больше произвольные указатели, в модели large состоящие из сегмента и смещения, а их же компилятор при компиляции этого кода буквально следовал стандарту и сравнивал только смещения, потому что ни один объект не может пересекать границу сегмента (в отличие от модели huge).

 Профиль  
                  
 
 
Сообщение15.01.2008, 06:12 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
e2e4 писал(а):
Извините, существуют стандартное понятие "C-calls", "Pascal-calls" и т.д.

Вы точно не путаете стандарты и традиции? Лично для меня монополист ещё не является организацией по стандартизации.

e2e4 писал(а):
Если бы порядок параметров зависел от реализации компиляторов, фигня бы получилась при попытке написания программы, собираемой из объектников, скомпилированных с разных языков.

Ну что Вам сказать (вздох…) Даже объектные модули С++ из под Борланда, Антилопы Гну и Микромягких плохо собираются вместе. А уж разных языков… Соглашение о вызове — это последняя беда. Частенько нужно как-то среду (runtime) загрузить и проинициализировать. Вот тут как раз и начинается кино // И подливает в это блюдо остроты то, что все компиляторы пишутся людьми с большим Эго, которые считают себя самыми крутыми, свой язык — лучшим, а потому все должны подпрыгивать вокруг них.

e2e4 писал(а):
Почти на 100% можно быть уверенным, что разрядность int соответствует разрядности АЛУ процессора.

Это не так. Я уже объяснял, почему. На сегодняшний день я считаю нормой памяти для нового ноутбука 2ГБ, десктопа — 4ГБ. Это автоматически значит для меня 64-бит указатель, 32 бит int.

e2e4 писал(а):
Малюсенькое, не имеющее к делу замечание: INTPTR лучше определить через typedef.

Я демонстрировал победный стиль, а не то, что я бы написал в реальном коде. Так что, см. ниже.

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

e2e4 писал(а):
Вообще-то, не уверен в корректности возвращения указателя на константную строку, определяемую в функции, из которой этот указатель возвращается,

Это-то как раз корректно. В коде много других огрехов: например, стандарт не гарантирует, что (int) NULL == 0 :), есть неявное преобразование int ** в int* и т.п.

А константная строка находится в статической памяти, поэтому такой номер законен.

Вообще говоря, в С есть много радостей. В частности, выражение
Код:
char* a = "победа!\n"; a[0] = ' '; a[1] = ' '; printf("победа!\n");
вполне законно. Что будет происходить дальше, сильно зависит от реализации. В частности, некоторые реализации, зная о практике модификации литералов, предлагают режим компиляции, при котором каждая литеральная строка используется только однажды.

e2e4 писал(а):
действительно использовалось 2 последовательных вызова ф-ии

Если предполагать, что адрес может быть со знаком (конкретно, чтобы меня не обвиняли в том, что так не бывает: Inmos'овские транспьютеры), то может понадобиться три вызова.

e2e4 писал(а):
Т.е. с указателями определены операции сложения/вычитания без явного их приведения к целому типу. Результат операции - целое число.

(1) См. выше (tolstopuz).
(2) Определены операции сложения указателя с целым и вычитания двух указателей. Сложения указателей нет.
(3) Кроме того, результат вычитания — не целое число, а число типа ptrdiff_t, который обязан быть знаковым, но не обязан быть int. Более того, никто не гарантирует (хотя это, разумеется, маловероятно), что при вычитании указателей не произойдёт переполнение. Не может быть? Может! Представьте себе, что Вы пишите программу распределения памяти, которой принадлежит почти вся память в начальный момент.

e2e4 писал(а):
я блоки не пишу, я ими думаю

Эт' хорошо. Существует два ответа:
1) потребность в синтаксических блоках резко уменьшается, когда каждая ветка ветвления, каждое тело цикла автоматически становится блоком. Приведу пример из С++:
Код:
for (int x = 0; ;) {break;}
for фактически определяет блок (и время жизни переменной). Но в С++ это — ублюдок (в первоначальном смысле этого слова), а представьте себе, как это выглядит, когда логично проведено через весь язык…
2) такой блок (имеющий законченную семантику) логично выделить в процедуру.
3) :) Так не отнимает у Вас блок никто. Просто он Вам нужен гораздо реже.

 Профиль  
                  
 
 
Сообщение17.01.2008, 16:46 


21/03/06
1545
Москва
незваный гость писал(а):
e2e4 писал(а):
Извините, существуют стандартное понятие "C-calls", "Pascal-calls" и т.д.

Вы точно не путаете стандарты и традиции? Лично для меня монополист ещё не является организацией по стандартизации.

Да, видимо Вы правы. Я просто не знал, что это не стандартизовано.

незваный гость писал(а):
e2e4 писал(а):
Почти на 100% можно быть уверенным, что разрядность int соответствует разрядности АЛУ процессора.

Это не так. Я уже объяснял, почему. На сегодняшний день я считаю нормой памяти для нового ноутбука 2ГБ, десктопа — 4ГБ. Это автоматически значит для меня 64-бит указатель, 32 бит int.

Ну дык и я о том же. 32-битный АЛУ - 32 бит int (это не стандарт, но оч. близко к нему). Разрядность указателя же в общем случае отличается от разрядности АЛУ (и почти всегда совпадает с разрядностью адресного регистра), естественно.

незваный гость писал(а):
В коде много других огрехов: например, стандарт не гарантирует, что (int) NULL == 0

Кстати, да. Лучше было бы
Код:
foo ( "%s\n", (int *)0 )
.

незваный гость писал(а):
На сегодняшний день я считаю нормой памяти для нового ноутбука 2ГБ, десктопа — 4ГБ.

Оффтоп:
Кстати, насколько я помню Вы работаете в Линухе? Я вот сижу под виндоус, а тут сложилась парадоксальная ситуация: XP 32 бит больше 3 с копейками Гб суммарно не отдает пользовательским процессам, XP 64 бит до сих пор не имеет многих необходимых драйверов, поэтому неюзабельна, Vista - вообще выкидышь. Так что я пока ограничился на десктопе 1,5 Гб :).
незваный гость писал(а):
А константная строка находится в статической памяти, поэтому такой номер законен.

Констатная строка находится в сегменте .const (инициализированных констант) в общем случае. Этот сегмент вполне может находится в недоступной нашей программе области памяти: например, в Гарвардской архитектуре с разделенной программной памяти и памяти данных, стандартен такой прием для Си-компиляторов: область инициализированных данных (.const - попадает в нее) находится в программной памяти, в качестве которой может выступать flash; в ф-ии c_init(), вызываемой до main() по умолчанию происходит копирование всех инициализированных данных в область памяти данных (а в самом Си может и не быть средств для работы с памятью программ). В принципе, ничего страшного, но представьте себе компилятор, который осуществляет это копирование не один раз в начале, а каждый раз при заходе в функцию, в стек? А если он еще и обладает крутым оптимизатором, который просто выкинет копирование этих константных строк, как только увидит, что они нигде внутри ф-ии не используются?

незваный гость писал(а):
Вообще говоря, в С есть много радостей. В частности, выражение
Код:
char* a = "победа!\n"; a[0] = ' '; a[1] = ' '; printf("победа!\n");

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

Ага, в Bulder'е что-то смутно вспоминаю такое при работе с AnsiString. Там есть какая-то конструкция, которая возвращает указатель на строку, который действителен только при первом (!) использовании, причем не обязательно для модификации - просто для чтения :).

незваный гость писал(а):
...
Кроме того, результат вычитания — не целое число, а число типа ptrdiff_t, который обязан быть знаковым, но не обязан быть int.

Извините, а каким же он еще, кроме как целым (не обязательно int, я не говорил об int) может быть???

незваный гость писал(а):
потребность в синтаксических блоках резко уменьшается, когда каждая ветка ветвления, каждое тело цикла автоматически становится блоком. Приведу пример из С++:
Код:
for (int x = 0; ;) {break;}

for фактически определяет блок (и время жизни переменной). Но в С++ это — ублюдок (в первоначальном смысле этого слова), а представьте себе, как это выглядит, когда логично проведено через весь язык…

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

Еще блок может быть единицей оптимизации, поэтому разбивать функцию на несколько блоков со своими переменными бывает полезно, хотя кто думает об этом в эпоху 4-х ядренных процессоров? :).

 Профиль  
                  
 
 
Сообщение18.01.2008, 21:27 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
e2e4 писал(а):
Кстати, да. Лучше было бы
Код:
foo ( "%s\n", (int *)0 )

Нет!!! (int*) 0 может быть законным адресом переменной. Лучше вместо !v писать v != NULL

e2e4 писал(а):
Констатная строка находится в сегменте .const (инициализированных констант) в общем случае

Это вопрос реализации. Вы, должно быть, и сами уже заметили, что оперируете терминами объектного кода, а не языка. Хотите верьте, хотите — нет, но стандарта и здесь нет. Хоть имя .const и является обычным, я встречал компиляторы, в которых это не так.

То, о чём Вы говорите далее: п. 3.1.4 стандарта говорит о том, что «многобайтное значение» используется для инициализации массива со статическим временем жизни». Согласно п. 3.1.2.4 инициализация такого объекта происходит лишь однажды, до начала исполнения программы.

e2e4 писал(а):
Извините, а каким же он еще, кроме как целым (не обязательно int, я не говорил об int) может быть???

Вы действительно не говорили. К сожалению, Ваша фраза была несколько двусмысленной, и я решил уточнить. Тем не менее, ptrdiff_t всё равно лучше.

e2e4 писал(а):
Еще блок может быть единицей оптимизации, поэтому разбивать функцию на несколько блоков со своими переменными бывает полезно,

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

Блок становится существенно важнее в С++, где конструкция { T t; ... } создаёт вызов конструктора и автоматический гарантированный вызов деструктора.

 Профиль  
                  
 
 
Сообщение23.01.2008, 01:02 
Заслуженный участник


31/12/05
1407
незваный гость писал(а):
Нет!!! (int*) 0 может быть законным адресом переменной.
"An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function."

 Профиль  
                  
 
 
Сообщение23.01.2008, 04:19 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
tolstopuz писал(а):
"An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. (33) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function."
(я вставил сноску)
Уели! То есть просто стандартом по голове!

Если серьёзно, спасибо! Я нашёл это место (и, заодно, обнаружил, что у меня не хватает половины индекса в стандарте, пришлось листать). П. 3.2.2.3. Но… Читаем стандарт дальше:
там же, сноска 33 писал(а):
The macro NULL is defined in <stddef.h> as a null pointer constant, see 4.1.5


Открывам по ссылке, и читаем:
4.1.5 писал(а):
The macros are

NULL

which expands to an implementation-defined null pointer constant; …
А это уже явно противоречит предыдущему тексту: трудно назвать 0 определяемым реализацией.

Похоже, здесь стандарт противоречит себе. Как практик, я понимаю обе стороны вопроса: с одной стороны, 9/10 программ и программистов закладывается на традиционное NULL = 0. С другой стороны, что делать в тех системах, в которых 0 — это середина памяти? Не писать на С?! Запрещать переменную в середине адресного пространства (в частности, не разрешать там стек)?! Вот и говорят, что NULL, вообще говоря, не 0.

Я согласен, такие системы — экзотика. Но давайте посмотрим PC. Где находятся (в момент загрузки) вектора прерываний? Правильно, по тому самому адресу 0, который нельзя использовать под переменные :) И разработчик просто плюёт на стандарт, и размещает их там, где предписано Богом и Интелом.

P.S. Во как я выкрутился! На самом-то деле я не знал, что стандарт (пусть и криво, но) рекомендует NULL == 0.

 Профиль  
                  
 
 
Сообщение24.01.2008, 01:23 
Заслуженный участник


31/12/05
1407
незваный гость писал(а):
4.1.5 писал(а):
The macros are

NULL

which expands to an implementation-defined null pointer constant; …
А это уже явно противоречит предыдущему тексту: трудно назвать 0 определяемым реализацией. Похоже, здесь стандарт противоречит себе.
Про это было много флейма. Общий вывод, насколько я понимаю, такой, что слова "implementation-defined" действительно излишни, и NULL согласно стандарту может определяться только как 0 или (void*)0.
незваный гость писал(а):
Как практик, я понимаю обе стороны вопроса: с одной стороны, 9/10 программ и программистов закладывается на традиционное NULL = 0. С другой стороны, что делать в тех системах, в которых 0 — это середина памяти?
Здесь все хорошо. Битовое представление нулевого указатателя не обязано быть нулевым. Так же как (float)4 обычно имеет другое битовое представление, чем 4, (void*)0 в такой архитектуре может показывать на любой заранее выбранный невалидный адрес. Мало того, в принципе null pointers разных типов могут иметь разные битовые представления. Вот здесь все это очень подробно расписано:

http://hebb.cis.uoguelph.ca/~dave/27320/c_faq.html
незваный гость писал(а):
Но давайте посмотрим PC. Где находятся (в момент загрузки) вектора прерываний? Правильно, по тому самому адресу 0, который нельзя использовать под переменные :)
А вот с этим хуже. "An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation." В принципе "except as previously specified" исключает из этого пункта (void*)0, потому что это явно оговоренный ранее null pointer value. Но, с другой стороны, нигде не написано, что null pointer value обязан являться trap representation. Но, с третьей стороны, он не равен ни одному "pointer to any object", а под "object" вроде как понимается все, что лежит в памяти, а не только то, что объявлено внутри программы: "region of data storage in the execution environment, the contents of which can represent values".

Правда, это актуально только при написании начальных загрузчиков. В нормальных программах, загружаемых под управлением ОС, под null pointer можно подложить что-нибудь ненужное, типа строки "Null pointer assignment".

 Профиль  
                  
 
 
Сообщение25.01.2008, 05:31 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Rationale 4.1.5 писал(а):

NULL can be defined as any null pointer constant. Thus existing code can retain definition of NULL as 0 or 0L, but an implementation may choose to define it as (void* )0; this later form of definition is convenient on architectures where the pointer size(s) do(es) not equal the size of any integer type. It was never been wise to use NULL in place of an arbitrary pointer as a function argument, however. since pointers to different types need not be the same size. The library avoids this problem by providing special macros for the arguments to signal, the only library function that might see a null function pointer.

Офигеть!

(1) Ясно, что имеется в виду под зависимостью от реализации — какую форму 0 выбрать.

(2) Оказывается, стандарт не гарантирует, что все указатели имеют одну длину, и более того, не гарантирует, что (void*) совместим с любым указателем.

Добавлено спустя 7 минут 43 секунды:

tolstopuz писал(а):
Битовое представление нулевого указатателя не обязано быть нулевым. Так же как (float)4 обычно имеет другое битовое представление, чем 4, (void*)0 в такой архитектуре может показывать на любой заранее выбранный невалидный адрес. Мало того, в принципе null pointers разных типов могут иметь разные битовые представления.

Это, конечно, верно. Очень верно (и, признаюсь, я об этом не думал). Но есть обратная сторона: подразумевается, что цена преобразования указатель / целое очень невысока. Этого неявного предположения нет в случае преобразования int / float.

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

Добавлено спустя 2 минуты 21 секунду:

В целом, крошечная программа позволила мне узнать много нового про указатели. :lol:

 Профиль  
                  
 
 
Сообщение25.01.2008, 12:22 
Заслуженный участник


31/12/05
1407
незваный гость писал(а):
Но есть обратная сторона: подразумевается, что цена преобразования указатель / целое очень невысока.
А с этим там еще интереснее: null pointer получается только при приведении к указателю _константного_ нуля. Так что, оказывается, и с производительностью все нормально, и для доступа к векторам прерываний лазейка есть.

http://c-faq.com/null/accessloc0.html

 Профиль  
                  
 
 
Сообщение30.01.2008, 02:38 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
tolstopuz писал(а):
null pointer получается только при приведении к указателю _константного_ нуля.

Из приведённого по ссылке текста я этого не понял. Более того, в свете дискуссии выше первые три способа выглядят сомнительными. (Третий — по двум причинам, из-за размера int, и из-за того, что стандарт не гарантирует наложения переменных в union).

Перечитал внимательно стандарт и обоснование. Действительно, преобразование целого в указатель зависит от реализации. Всюду подчёркивается, что константа 0 — это NULL указатель. О том, что перевод переменной, содержащей 0, в указатель может дать не NULL, не говорится явно нигде. Захватывает дух! но получается, что (void*) 0 — это изображение NULL, а вовсе не cast, как могло бы показаться непосвящённым.

 Профиль  
                  
 
 
Сообщение30.01.2008, 12:17 
Заслуженный участник


31/12/05
1407
незваный гость писал(а):
Захватывает дух! но получается, что (void*) 0 — это изображение NULL, а вовсе не cast, как могло бы показаться непосвящённым.
Да! :)

 Профиль  
                  
 
 
Сообщение04.02.2008, 00:54 
Заслуженный участник


18/03/07
1068
Интересные головоломные примеры кода нашёл здесь и здесь. По ссылкам побродить тоже было интересно.

Прошу прощения, если про это мероприятие участникам темы уже было известно.

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

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



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

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


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

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