LinuxForum.Fans
Тема

указатели и массивы в C (для начинающих)

Автор: drBatty 23 сообщений 13300 просмотров 📌 закреплена
04.10.2013 11:07 · отредактировано #341353

Напишу-ка я небольшую темку.

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

Важно понимать, что в стандарте не определёна никакая "память", и никакие "адреса" в ней. Потому, вышеуказанное "определение" нужно считать просто как объяснением ситуации, в большинстве современных CPU.

Как определить указатель?

text
int value = 17;
int *pointer = &value;

Здесь я определил сначала целое value, а затем указатель(pointer) на это value.

Указатель можно переназначить на другую переменную

text
int value2 = 236;
pointer = &value2;

Как и зачем использовать указатели?

В большинстве случаев, указатели нужны для косвенной адресации. Дело в том, что по правилам сишечки, функции принимают ЗНАЧЕНИЯ аргументов. И потому, функция не может работать с самими аргументами. Например, мы хотим написать функцию, которая меняет местами значения x<->y. Если мы напишем

text
void swap1(int x, int y)
{
 int tmp = x;
 x = y;
 y = tmp;
}

то всё будет работать, но значения местами НЕ поменяются. Потому-что внутри функции СВОИ int x и int y.

Для решения, надо просто передать указатели:

text
void swap1(int *x, int *y)
{
 int tmp = *x;
 *x = *y;
 *y = tmp;
}

Вот теперь всё работает

text
#include <stdio.h>

void swap2(int *x, int *y) { · int tmp = *x; · *x = *y; · *y = tmp; }

int main() { · int v1 = 25; · int v2 = 51; · swap2(&v1, &v2); · printf("v1: %d, v2: %d\n", · · · v1, · · · v2); · return 0; }

Указатель на NULL

Указатель может указывать В НИКУДА. Для этого туда надо записать NULL. Как-то так:

text
int *pinter = NULL;

Важно: не стоит путать NULL с нулём. Это разные вещи! В стандарте прямо сказано, что значение указателя NULL не определено (точнее, определено, но для каждой конкретной системы по своему. Часто оно действительно 0, потому ваш быдлокод, где вы путаете тёплое с мягким, иногда даже будет работать)

И тем не менее, в стандарте также определено, что преобразовав NULL в целое, вы гарантированно получите ноль. Если-же преобразовать в целое указатель, который на что-то указывает, вы гарантированно получите НЕ НОЛЬ (Важно: вы получите НЕ НОЛЬ, но вовсе НЕ обязательно, что вы получите адрес, или вообще хоть что-то осмысленное! Вы получите ☣, которая не ноль). Таким образом, вы можете узнать, что указатель куда-то указывает

text
if(pointer)
  // указатель куда то указывает
else
  // указатель никуда не указывает, и равен NULL

Например вы можете попросить систему(точнее glibc) найти вам символ в строке(man 3 strchr), и она вам отдаст указатель на этот символ если найдёт, и даст NULL, если не сможет найти.

Если разименовать NULL (посмотреть, на что оно указывает), то мир сдвинется с места. Тоже самое произойдёт если поделить на ноль. В программирование же ничего страшного не случается, обычно ваш быдлокод банально рухнет. Впрочем, случится может всё, что угодно. (кому угодно? кому угодно.)

Опасайся ☣ !

Указатель void*

Каждый указатель имеет свой тип, например выше у нас были указатели, которые указывали на int. Иногда нужны указатели, которые указывают на что угодно. Называются они void*.

Естественно, любой указатель можно без вопросов перевести в void*. Обратно надо переводить в явном виде(если надо).

Если у вас был указатель одного типа(не void*), а вы его перевели в указатель другого типа(не void*), то всё будет хорошо. До тех пор пока вы его не использовали. Тогда случится ☣.

Опасайся ☣ !

Указатель на указатель

Указатель может указывать на что угодно. В том числе и на другой указатель. Это особый тип, который называется "указатель на указатель". Типа того:

text
int value = 1;
int *pointer = &value;
int **pp = &pointer;

Нужно больше const!

Вообще говоря, для обычных переменных const не слишком нужен(они переменные, К.О.) Однако, для указателей всё меняется, ибо один указатель можно менять двумя способами:
1. можно передвинуть указатель на другое число
2. можно изменить само число, на которое указывает указатель.
Поэтому существует 4 типа указателей разной степени константности:
1. обычный указатель
2. указатель на константу (нельзя менять значение)
3. константный указатель(нельзя двигать указатель)
4. константный указатель на константу
Последний -- самый лучший. Ибо такой указатель никогда и никак не меняется. Самый плохой -- первый, ибо может меняться как угодно.

И да, можно преобразовать константный указатель в обычный. Получится ☣.

Опасайся ☣ !

Массивы в сишечке

Массивов в сишечке нет. Массивы в сишечке есть. Оба высказывания истинны. Странно, да?

На самом деле, ничего странного тут нет, ибо массивы конечно есть, но их смысл абсолютно другой, нежели в других ЯП. На самом деле, самая обычная переменная -- это УЖЕ массив из одного эл-та. Массив типа

text
int a[3]={1,2,3};

это три переменные, которые хранятся в памяти вплотную (это важно!)

Показать
Тут можно мне возразить про выравнивание, не нужно. Я в курсе. Потом как-нибудь. Обычно выровненное int == невыровненное int. Потому разницы здесь никакой. СРАЗУ за первой int начинается вторая int. Потому в данном случае всё у меня корректно.

Обращаться к такому массиву можно как и в остальных ЯП

text
a[1]=8;
printf("%d", a[2]);
int y = a[0];

Однако, надо помнить, что внутри [скобок] вовсе НЕ целое число, а некий "безразмерный тип", т.е. "просто количество", иначе "штуки". Называется такой тип size_t. Он имеет важное отличие от int -- в этом типе можно сохранить количество чего угодно (в рамках C). К примеру, можно сохранить длину любого массива. Ещё 10 лет назад это различие было очень слабо заметно, и быдлокодеры постоянно мешали size_t и int (как сейчас мешают NULL и 0). ВНЕЗАПНО появились 64х битные процессоры amd64(не только AMD, intel тоже), и теперь size_t вырос в 4 миллиарда раз (был 4 миллиарда, стал в 4 миллиарда раз больше. Ибо 64 бита). Не менее ВНЕЗАПНО(для быдлокодеров) тип int так и остался 32х битным. Такие дела...

Массивы и указатели

Массивы мистическим образом связанны с указателями. Это очень сильное колдунство. Как оно в магии IRL? Колдун говорит СЛОВА, самые обычные, которые может сказать каждый, и ВНЕЗАПНО кто-то умирает в страшных мучениях. В сишечке -- тоже самое. Абсолютно. В примере выше, вот это вот наша a -- указатель. И не указатель, а массив. Нет, никакой не массив, а указатель.

Ладно. На самом деле всё проще. На самом деле, a это всё-же массив, который по совместительству, одновременно, является ещё и указателем. Но не просто указателем, а с дополнительно прибитым гвоздями размером этого массива. Его можно даже узнать:

text
int main()
{
    int a[3];
    printf("%d\n",
            sizeof(a));
    return 0;
}

напишет 12. Почему 12? Потому-что 12 char'ов. В одном int'е 4 char'а (32 бита), т.е. 3*4==12. Естественно это только на i686 и amd64 так. На других платформах -- ☣.

Что-бы платформонезависимо посчитать размер массива, надо его поделить на sizeof(a[0]), т.е. размер первого эл-та. Который всегда есть. Получится естественно 3.

Нужно помнить, что это не просто особый указатель, а особый константный указатель. Т.е. определив массив, вы его уже НИКОГДА не сдвинете. Ну вот так вот.

Кроме того, нужно знать, что когда массив передаётся в функцию, он с помощью магии превращается в самый обычный указатель. Вот там(внутри функции) его размер будет всего 1(один эл-т, а не один байт). Как обычно, утерянная информация не возвращается. ВСЁ, что поглотила ☣ уже не вернуть. Эта та самая речка Лета, что рядом с адом протекает. Т.е. вернув из функции указатель, вы получите указатель(а не массив). И вернув массив, вы тоже получите указатель.

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

Опасайся ☣ !

Указатель в/на массив

Указатель в/на массив обладает особой магией. Связано это прежде всего с самими колдунами (а особенно с надмозгами-переводчиками, которые K&R переводили). Все 146% программистов сишечников говорит "указатель НА массив", но на самом деле, это указатель В массив. Само имя массива, это и есть такой указатель В массив. Что естественно не мешает наделать ещё Over9000 других указателей в/на массив. Эти другие указатели можно уже даже двигать. Чем мы сейчас и займёмся.

Но сначала, откроем тайну ещё одного великого колдунства, а именно -- [скобок]. Кажется, в них нет ничего таинственного, но так оно только для непосвящённых. Вот две строки:

text
int a[3];
a[1] = 7;

На первый взгляд, скобки одинаковые? Присмотритесь внимательно: во второй строке скобки СОВСЕМ ИНЫЕ. Не?

Да.

В первой строке скобки ОБЪЯВЛЯЮТ массив, и в них пишется его длинна. Во второй строке скобки разименовывают массив, и 7 пишется В этот массив. А вот единица тут играет роль сдвига a в сторону увеличения на 1. Потому 7 пишется не в *a, а во второй элемент массива.

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

text
*(a+1) = 7;

Вот тут начинающие волшебники очень любят рвать шаблон ещё более начинающим, любимой шуткой

text
1[a] = 7;

Да, этот быдлоколд работает. Почему? Потому-что a+1==1+a. Почему быдлокод? Да потому, что мы ещё НЕ ОПРЕДЕЛИЛИ сложение указателей и целых(точнее size_t, хотя это даже и не size_t, а пока для нас ☣). Ну а раз не определили, то и применять какие-то законы никак не можем.

Опасайся ☣ !

Арифметика указателей

Арифметика указателей -- суть тайная магия, неподвластная непосвящённым. На самом деле, она проста как кирпич. Рассмотрим пример:

text
int a[3]={111,222,333};
int *p1 = &a[0];
int *p2 = &a[2];
??? D = p2 - p1;// чему равно D и вообще, что это такое???

В этом примере, очевидно D равно 2 (2-0, потому-что расстояние от нулевого эл-та(111) до второго(333) очевидно будет 2). Но вот тип уже совсем НЕ очевиден. Это НЕ "штуки"(size_t), это что-то иное, ибо имеет физический смысл РАССТОЯНИЯ, а вовсе не количества. В сишечке для этого нечто есть тип ptrdiff_t.


Таким образом определяется вычитание указателей, дающее расстояние между ними, выраженное в ptrdiff_t. Важно отметить, что указатели ОБЯЗАНЫ указывать на один и тот же массив. В противном случае, при их вычитании, получается ☣.

Показать
Также в стандарте есть некое послабление: указатель может указывать не только на этот массив, но и на фиктивный элемент, сразу ЗА массивов. В нашем примере массива из трёх целых, возможно создание указателя в/на &a[3]. Естественно, такой указатель хоть и можно применять в арифметике, но ни в коем случае нельзя разименовывать!

Опасайся ☣ !

Определив таким образом вычитание, можно очевидно и определить сложение, через это вычитание. НО. Не нужно забывать про типы. Т.е. складывать указатели можно, но только с числами ptrdiff_t. К счастью, обычно целые числа без проблем преобразуются в этот самый ptrdiff_t. На самом деле, ptrdiff_t это и есть целое, такого размера, что в него может поместится любая разность между любыми указателями(допустимыми, на один и тот же массив. См. выше).

Сложение указателей в сишечке НЕ определено, как и остальные арифметические действия.

Опасайся ☣ !

Строки в сишечке

В C/C++ нет встроенного типа string(строка), в сишечке, вместо него используются массивы из char'ов. Дабы определить размер этого массива, используют символ NUL aka '\0' (не надо путать его с '0', и с NULL). Данный символ определяет конец строки.

Размер строки в char'ах (байтах/октетах обычно) определяется с помощью функции strlen(3).

Показать
STRLEN(3) Linux Programmer's Manual STRLEN(3)

NAME
strlen - calculate the length of a string

SYNOPSIS
#include <string.h>

size_t strlen(const char *s);

DESCRIPTION
The strlen() function calculates the length of the string s, excluding the termi-
nating null byte ('\0').

RETURN VALUE
The strlen() function returns the number of bytes in the string s.
(и далее см. man 3 strlen)

Также в glibc имеются и все прочие функции для таких строк. Ну а для определения таких строк имеется специальная форма

text
const char *str = "Hello world!";
char str[] = "Hello";

(ага, даже две. Вторая форма -- массив. Размер компилятор сам определит в данном случае)

Динамические массивы

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

Показать
точнее, в новом стандарте таки есть, но ИМХО они не нужны.

Что делать? Да как всегда -- просить ОС дать нужной памяти. Угу, через glibc, как обычно. Нам понадобятся (сначала) две функции: malloc(3) и free(3). Первая выделяет кусок памяти, вторая его возвращает обратно.

Важно: эти две функции должны быть РЯДОМ. Что-бы не забыть одну из них. Если вы забудете malloc(3), то ничего страшного -- ваша программа просто рухнет, т.к. вы используете память не выделив её предварительно. Рухнет она в GNU/Linux, а вообще, это как всегда ☣. Куда как страшнее, если вы забудете free(3) -- программа будет работать(во всяком случае первое время), но рано или поздно, пользователи заметят, что ваш быдлокод течёт. Т.е. жрёт память... Что удивительного, если вы заб(ы|и)ли её отдать обратно? Впрочем, тоже ничего страшного, подумаешь, канделябром по голове?

04.10.2013 12:13 #341368
"drBatty" написал:
Важно: не стоит путать NULL с нулём. Это разные вещи! В стандарте прямо сказано, что значение указателя NULL не определено (точнее, определено, но для каждой конкретной системы по своему. Часто оно действительно 0, потому ваш быдлокод, где вы путаете тёплое с мягким, иногда даже будет работать)

ЛПП, стандарт гарантирует, что любое целочисленное выражение со значением ноль равнозначно NULL (в контексте указателей).
Использование NULL вместо 0 — лишь вопрос стиля.

"drBatty" написал:
тем не менее, в стандарте также определено, что преобразовав NULL в целое, вы гарантированно получите ноль.

а вот этого как раз, строго говоря, не определяется...

text
int *ptr = NULL;
if( (intptr_t)ptr == 0 ) {
    // implementation defined
}
"drBatty" написал:
Последний -- самый лучший.

Это из серии шуруп [всегда] лучше гвоздя...

в остальном неплохо... вечерком подчищу флейм и солью с Как написать первую программу на С ?

04.10.2013 12:23 #341370

"Fat-Zer" написал:
и солью с Как написать первую программу на С ?
может не надо?
04.10.2013 12:26 #341374
"drBatty" написал:
может не надо?

почему нет?

04.10.2013 12:30 #341376
"Fat-Zer" написал:
ЛПП, стандарт гарантирует, что любое целочисленное выражение со значением ноль равнозначно NULL (в контексте указателей).

идёшь на... Читать стандарт.

Если ты не понял, мы тут про сишечку, а НЕ про плюсы.

В стандарте сишечки определено ПРЕОБРАЗОВАНИЕ целого нуля в указатель. Мне найти это место, или сам справишься?

"Fat-Zer" написал:
а вот этого как раз, строго говоря, не определяется...

за то определено преобразование внутри условия if(). Вполне строго. Что NULL рассматривается как false, а валидный указатель == true. (т.е. НЕ НОЛЬ). Неопределённость есть, и о ней я не забыл:

"drBatty" написал:
вы получите НЕ НОЛЬ, но вовсе НЕ обязательно, что вы получите адрес, или вообще хоть что-то осмысленное! Вы получите ☣, которая не ноль
"Fat-Zer" написал:
Это из серии шуруп [всегда] лучше гвоздя...

твоё сравнение мимо кассы. Шуруп и гвоздь -- разные сущности. Их нельзя сравнивать на "лучше", как нельзя сравнивать на зелёность тёплое и мягкое.

А вот константный указатель на константу -- он самый лучший в том смысле, что ты лучше всего знаешь, ЧТО с ним произойдёт (а что с ним будет-то?)

04.10.2013 12:33 #341378

"Fat-Zer" написал:
почему нет?
потому-что ИМХО это совершенно отдельная тема.
И если про первую программу нужна всем, кто пишет первую программу, то эта нужна некоторому проценту тех, кто уже достаточно опытный программист на сишечке (судя по форумам, число таких составляет ~146%).
04.10.2013 14:39 #341401
"drBatty" написал:
Если ты не понял, мы тут про сишечку, а НЕ про плюсы.

плюсы это отдельная тема... там вообще использование NULL советуют избегать...

"drBatty" написал:
В стандарте сишечки определено ПРЕОБРАЗОВАНИЕ целого нуля в указатель.

само собой... поэтому целочисленный ноль можно использовать везде вместо NULL...
просто надо разделять понятия «нулевой указатель» и «макрос NULL».

"drBatty" написал:
потому-что ИМХО это совершенно отдельная тема.И если про первую программу нужна всем, кто пишет первую программу, то эта нужна некоторому проценту тех, кто уже достаточно опытный программист на сишечке (судя по форумам, число таких составляет ~146%).

ладно... пусть будет в отдельной.... хотя ИМХО это логичное продолжение той темы, без понимания этого написать что-либо разумное невозможно...

04.10.2013 14:50 #341402
"Fat-Zer" написал:
плюсы это отдельная тема... там вообще использование NULL советуют избегать...
ну да, есть такие рекомендации. Хотя я(и не только я) с этим не согласен. PHP и Java из C++ они всё равно не сделают, а только внесут лишнюю сложность в вопрос.

"Fat-Zer" написал:
само собой... поэтому целочисленный ноль можно использовать везде вместо NULL...
просто надо разделять понятия «нулевой указатель» и «макрос NULL».
да, "можно". Вот только и забывать не нужно, что указатель на ноль, и целое ноль -- не одно и то же. ИМХО необходимо особо подчеркнуть, что в сишечке есть ТИП. Вот ЕСТЬ он, и всё тут! И то, что этот самый тип так причудливо сам по себе изменяется, так это особая магия, а вовсе не то, что как ты говоришь "одно и тоже". Нет. Не одно и то же, а совершенно разные вещи.

И да, "макрос NULL" на самом деле в классике (void*)0. (хотя в GNU не так).

"Fat-Zer" написал:
ладно... пусть будет в отдельной.... хотя ИМХО это логичное продолжение той темы, без понимания этого написать что-либо разумное невозможно...

туда можно ссылку засунуть. И так ИМХО tl;dr получается. И там и там.

04.10.2013 16:18 #341422
"drBatty" написал:
а вовсе не то, что как ты говоришь "одно и тоже"

перевирать не надо... я не говорил «одно и тоже»...

"drBatty" написал:
И да, "макрос NULL" на самом деле в классике (void*)0.

ЕМНИП у K&R он как раз обычный, целочисленный 0, а такое стандарт просто допускает...

"drBatty" написал:
твоё сравнение мимо кассы. Шуруп и гвоздь -- разные сущности. Их нельзя сравнивать на "лучше", как нельзя сравнивать на зелёность тёплое и мягкое.А вот константный указатель на константу -- он самый лучший в том смысле, что ты лучше всего знаешь, ЧТО с ним произойдёт (а что с ним будет-то?)

хорошо... возьму другую метафору: ключ на 19 самый лучший...

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

04.10.2013 16:28 #341423
"Fat-Zer" написал:
перевирать не надо... я не говорил «одно и тоже»...

говорил "равнозначно". ИМХО многие поймут как "одно и то же". Нельзя так говорить, особенно новичкам.

"Fat-Zer" написал:
ЕМНИП у K&R он как раз обычный, целочисленный 0, а такое стандарт просто допускает...

это тоже не играет роли, что там у K&R было.

"Fat-Zer" написал:
хорошо... возьму другую метафору: ключ на 19 самый лучший...

лучше чем?..

"Fat-Zer" написал:
да, и константные указатели в чистом виде используются чуть чаще, чем никогда...
ага. Чуть более чем все массивы == константные указатели.

Читай дальше... А у меня тут дела, потом допишу...
08.10.2013 18:30 #342344
"drBatty" написал:
Нельзя так говорить, особенно новичкам.

что 0 вместо NULL в коде нельзя использовать тоже говорить не хорошо... а именно так звучит то что ты писал...

"drBatty" написал:
лучше чем?..

вот меня тоже интересует, чем «константный указатель на константу» лучше остальных...

"drBatty" написал:
ага. Чуть более чем все массивы == константные указатели.
"Fat-Zer" написал:
да, и константные указатели в чистом виде используются чуть чаще, чем никогда...
"drBatty" написал:
Читай дальше... А у меня тут дела, потом допишу...

про массивы потом просмотрю...

08.10.2013 18:38 #342348

Важно: не стоит путать NULL с нулём. Это разные вещи! В стандарте прямо сказано, что значение указателя NULL не определено (точнее, определено, но для каждой конкретной системы по своему. Часто оно действительно 0, потому ваш быдлокод, где вы путаете тёплое с мягким, иногда даже будет работать)

И тем не менее, в стандарте также определено, что преобразовав NULL в целое, вы гарантированно получите ноль. Если-же преобразовать в целое указатель, который на что-то указывает, вы гарантированно получите НЕ НОЛЬ (Важно: вы получите НЕ НОЛЬ, но вовсе НЕ обязательно, что вы получите адрес, или вообще хоть что-то осмысленное! Вы получите ☣, которая не ноль). Таким образом, вы можете узнать, что указатель куда-то указывает

"Fat-Zer" написал:
что 0 вместо NULL в коде нельзя использовать тоже говорить не хорошо... а именно так звучит то что ты писал...
что я неправильно написал?
08.10.2013 19:02 #342355
"drBatty" написал:
что я неправильно написал?

только если то, что в одну куча свалил «нулевой указатель» и «макрос NULL».
И ещё то, что ты, кажется, обвиняешь в быдлокодинге и нарушении стандарта всех кто использует 0 вместо NULL в коде...

и ещё про это говорил, что не точно:

"drBatty" написал:
И тем не менее, в стандарте также определено, что преобразовав NULL в целое, вы гарантированно получите ноль.

строго говоря и согласно стандарту:

text

if (NULL) { /* false*/ }
if ((int)NULL) { /* implementation-defined */ }
08.10.2013 19:06 #342356
"Fat-Zer" написал:
вот меня тоже интересует, чем «константный указатель на константу» лучше остальных...

очевидно жеж! Чем меньше ☣, тем лучше!

Ты прочитал такую простыню, и ниосилил рефрен?

Суть в том, что чем больше степеней свободы, тем сложнее система, и тем капризнее она и своенравней. И тем меньше она нужна.

Ну сам посуди: если ты видишь const, ты знаешь, что "это не меняется". Разв это плохо? Легче читать, легче поддерживать. А уж писать так вообще просто. Если я вижу код без const, я знаю на 146% -- написано новичком, который не просидел ни одной бессонной ночи, отлаживая свой кривой код.

Ну да, может он синтаксис C с молоком матери всосал, а папа на ночь ему стандарт пел?

"Fat-Zer" написал:
про массивы потом просмотрю...

посмотри...

PS: кстати. Сейчас тенденция такова, что NULL так вообще считай отменили. Теперь NULL это __null типа nullptr_t. Вот так вот...

08.10.2013 19:11 #342357
"Fat-Zer" написал:
только если то, что в одну куча свалил «нулевой указатель» и «макрос NULL».
разве это не одно и то же?

"Fat-Zer" написал:
И ещё то, что ты, кажется, обвиняешь в быдлокодинге и нарушении стандарта всех кто использует 0 вместо NULL в коде...
ну а не правильно? Целое число != указатель. Это как тёплое и мягкое. Замороженный лёд не тёплый и не мягкий, да. Однако, это не пруф того, что мягкое можно сравнивать с тёплым.

"Fat-Zer" написал:
if ((int)NULL) { /* implementation-defined */ }

разве?

09.10.2013 09:09 #342521
"drBatty" написал:
разве это не одно и то же?
"drBatty" написал:
ну а не правильно? Целое число != указатель. Это как тёплое и мягкое. Замороженный лёд не тёплый и не мягкий, да. Однако, это не пруф того, что мягкое можно сравнивать с тёплым.

не совсем одно и то же... стандарт рассматривает их как разные сущности...
NULL (как и целочисленный 0 и (void*)0) — это «null pointer constant». для него определено то, что он должен преобразовываться в нулевой указатель (null pointer), внутреннее представление, зависящее от реализации...

"drBatty" написал:
if ((int)NULL) { /* implementation-defined */ }
разве?

строго говоря, это не гарантируется т.к. преобразование указатель->int даёт НЁХ, никаких оговорок на счёт нулевых указателей нет.
ИРЛ, конечно, должно работать везде...

"drBatty" написал:
PS: кстати. Сейчас тенденция такова, что NULL так вообще считай отменили. Теперь NULL это __null типа nullptr_t. Вот так вот...

не путай си с плюсами... а __null может быть только если внутренним символом компилятора/libc.
хотя C11 я пока не видел...

"drBatty" написал:
Ну сам посуди: если ты видишь const, ты знаешь, что "это не меняется". Разв это плохо? Легче читать, легче поддерживать. А уж писать так вообще просто. Если я вижу код без const, я знаю на 146% -- написано новичком, который не просидел ни одной бессонной ночи, отлаживая свой кривой код.

я согласен с тезисом: «указатель, передаваемый в функцию, должен указывать на константу, если функция не меняет данные», но делать все указатели константыми ИМХО бесполезное излишество... не могу даже представить от какой ошибки это может уберечь... да и синтаксис у них глаза сломаешь...

по поводу массивов, только одно замечание:

"drBatty" написал:
Да потому, что мы ещё НЕ ОПРЕДЕЛИЛИ сложение указателей и целых

на самом деле оно определено...

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

09.10.2013 11:37 #342545
"Fat-Zer" написал:
не совсем одно и то же... стандарт рассматривает их как разные сущности...
NULL (как и целочисленный 0 и (void*)0) — это «null pointer constant». для него определено то, что он должен преобразовываться в нулевой указатель (null pointer), внутреннее представление, зависящее от реализации...

насколько я понимаю стандарт, то NULL это такая сущность, которая переходит в null pointer, если засунуть его в указатель. Также указатель можно сравнивать с NULL, и если в указатель запихали NULL предварительно, то указатель будет равен NULL.

Т.е. разница конечно есть, но она точно такая же, как разница между int x равным 5, и константой 5L.

При этом с NULL и 0L такой связи нету, и быть не может, ибо NULL это совсем другое. И указатель равный NULL это совсем не тоже самое, что 0.

"Fat-Zer" написал:
строго говоря, это не гарантируется т.к. преобразование указатель->int даёт НЁХ, никаких оговорок на счёт нулевых указателей нет.

Это достаточно кривое и скользкое место в стандарте. Оговорки таки есть. Но мне лениво их искать, и да, это действительно именно "оговорки".

"Fat-Zer" написал:
ИРЛ, конечно, должно работать везде...

ну например в i8086 оно не всегда будет работать. Ибо там 4096 NULL, и только один из них равен NULL. А в компиляторе, как обычно, никто и не подумал ставить костыли, которые в рантайме определяют NULL это или не NULL. Уверен, что это не единственный такой процессор. Конечно, даже если это UB и не UB, то всё равно такой UB-NULL может появится _только_ если выше по коду УЖЕ было такое UB. В этих строках это прошлое UB просто вскроется, и всё поломает.

"Fat-Zer" написал:
не путай си с плюсами... а __null может быть только если внутренним символом компилятора/libc.
хотя C11 я пока не видел...
я не путаю. Это теперь расширение GCC. Да, БЕЗ плюсов. Вот так вот, ВНЕЗАПНО...
09.10.2013 11:47 #342550
"Fat-Zer" написал:
я согласен с тезисом: «указатель, передаваемый в функцию, должен указывать на константу, если функция не меняет данные», но делать все указатели константыми ИМХО бесполезное излишество... не могу даже представить от какой ошибки это может уберечь... да и синтаксис у них глаза сломаешь...

понимаешь в чём тут дело: пока у тебя просто тупо указатели, то их константность особо и не играет роли. Но всё меняется, когда приходят ОНИ. Указатели на массивы, и указатели на указатели. Ну и всё остальное. Вот тут -- согласись, чёрт ногу сломит. И попросту невозможно писать такой код (а другого не бывает), если у тебя не просто хрен знает что, а ещё такое хрен знает что, которое В ЛЮБОЙ МОМЕНТ КАК УГОДНО И КОМУ УГОДНО МОЖЕТ ИЗМЕНИТСЯ. И для тебя будет неожиданным ЛЮБОЕ изменение, кроме того, на которое ты рассчитываешь. И вот тут-то ты и помянешь меня добрым словом (или помянешь недобрым мамку того быдлокодера, который написал void ***x; )

"Fat-Zer" написал:
на самом деле оно определено...
на тот момент изложения -- ещё нет.

"Fat-Zer" написал:
ещё можно было про массивы переменной длинны упомянуть...
что ЭТО быдлокод по определению? Можно...
20.10.2013 20:16 #344636

чего-то я давно не заходил... даже удивлён, что тебя ещё не забанили=)

"drBatty" написал:
Ибо там 4096 NULL, и только один из них равен NULL.

не всё то NULL, что ссылается на нулевой адрес...
если вспоминать про 16-ти битные x86, то можно ещё устроить демагогию о его атавизмах, как модели памяти... и при какой-как будут реализоваться указатели... мне, откровенно говоря, лень...

"drBatty" написал:
я не путаю. Это теперь расширение GCC.

на доку не сошлёшься? чего-т я такого не помню...

"drBatty" написал:
Но всё меняется, когда приходят ОНИ. Указатели на массивы, и указатели на указатели. Ну и всё остальное. Вот тут -- согласись, чёрт ногу сломит.

ладно, уговорил.... константные указатели самые поездатые...

"drBatty" написал:
что ЭТО быдлокод по определению? Можно...

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

03.11.2013 14:23 #346926
"Fat-Zer" написал:
на доку не сошлёшься? чего-т я такого не помню...

/usr/lib64/gcc/x86_64-slackware-linux/4.8.1/include/stddef.h

text
/* A null pointer constant.  */

#if defined (_STDDEF_H) || defined (__need_NULL) #undef NULL·· /* in case <stdio.h> has defined it. */ #ifdef __GNUG__ #define NULL __null #else /* G++ */ #ifndef __cplusplus #define NULL ((void *)0) #else /* C++ */ #define NULL 0 #endif /* C++ */ #endif /* G++ */ #endif· /* NULL not defined and <stddef.h> or need NULL. */ #undef· __need_NULL

такой вот у меня NULL

"Fat-Zer" написал:
константные указатели самые поездатые...

просто от них меньше всего проблем.

"Fat-Zer" написал:
чем же?
ненужностью.
Войдите или зарегистрируйтесь, чтобы ответить.