LINUX.ORG.RU

Pointers in C - правильно ли я их понимаю?

 ,


0

1

Изучаю си (в которых раз :). Вот обучающий код:

#include <stdio.h>

int main(int argc, char *argv[])
{
    // create two arrays we care about
    int ages[] = {23, 43, 12, 89, 2};
    char * names[] = {
        "Alan", "Frank",
        "Mary", "John", "Lisa"
    };

    // safely get the size of ages
    int count = sizeof(ages) / sizeof(int);
    int i = 0;

    // first way using indexing
    for(i = 0; i < count; i++) {
        printf("%s has %d years alive.\n",
                names[i], ages[i]);
    }

    printf("---\n");

    // setup the pointers to the start of the arrays
    int *cur_age = ages;
    char **cur_name = names;

    // second way using pointers
    for(i = 0; i < count; i++) {
        printf("%s is %d years old.\n",
                *(cur_name+i), *(cur_age+i));
    }

    printf("---\n");

    // third way, pointers are just arrays
    for(i = 0; i < count; i++) {
        printf("%s is %d years old again.\n",
                cur_name[i], cur_age[i]);
    }

    printf("---\n");

    // fourth way with pointers in a stupid complex way
    for(cur_name = names, cur_age = ages;
            (cur_age - ages) < count;
            cur_name++, cur_age++)
    {
        printf("%s lived %d years so far.\n",
                *cur_name, *cur_age);
    }

    return 0;
}

http://c.learncodethehardway.org/book/ex15.html

Итак, есть еще пятый способ перечисления данных, который наиболее мне понятен:

    // five way using indexing
    for(i = 0; i < count; i++) {
        printf("%s has %d years alive.\n",
                *(names + i), *(ages + i));
    }

Таким образом names[i] это сокращение на *(names + i). Объясняется очень просто: массив в си это список, разделенный на блоки указанного размера, при этом обращение напрямую по имени переменной означает на начальный адресс в памяти. Суммирование этого адреса с числом означает, автоматическое умножение числа на размер типа данных массива. Для int ages[], ages + i означает адрес ages + i * sizeof(int). Именно поэтому cur_name[i], cur_age[i] по смыслу это тоже самое что и names[i], ages[i], но ведь в одном случае нам вталкивают что это «массивы», а во втором, что это ссылки на эти массивы. Но на деле оба являются ссылками, или адресами на участки памяти.

Поэтому записи указателей int *cur_age; char **cur_name; на самом деле ничего общего не имеют с инициализацией (определением) собственно массивов, особенно таких char * names[] (список указателей на статичные литералы), а *(names + i) на самом деле просто разыменовывает указатель на эту статичную строку и возращает ее значение. В то время как char letters[] - совсем иная структура данных (список из одного char). Разница между ними в том, что сдвиг адреса будет иметь разное количественно значение.

    // print addresses of names
    printf("names, sizeof(char *): %lu\n", sizeof(char*));

    for (i = 0; i < count; ++i) {
        printf("names's element %d has address %lx and value '%s'\n",
                i, (size_t)(names + i), *(names + i));
    }

    // same as above, but for letters
    char letters[] = {
        'A', 'B', 'C'
    };

    printf("letters, sizeof(char): %lu\n", sizeof(char));

    for (i = 0; i < sizeof(letters); ++i) {
        printf("names's element %ud has address %lx and value '%c'\n",
                i, (size_t)(letters + i), *(letters + i));
    }

Как правильно выводить адреса (сейчас варнинг: cast from pointer to integer of different size) пока не знаю, но скоро узнаю :)

Пруф:

names, sizeof(char *): 8
names's element 0 has address ff0003c0 and value 'Alan'
names's element 1 has address ff0003c8 and value 'Frank'
names's element 2 has address ff0003d0 and value 'Mary'
names's element 3 has address ff0003d8 and value 'John'
names's element 4 has address ff0003e0 and value 'Lisa'
letters, sizeof(char): 1
names's element 0d has address ff0003b0 and value 'A'
names's element 1d has address ff0003b1 and value 'B'
names's element 2d has address ff0003b2 and value 'C'

Все верно? Почему везде об этом не говорится по-человечески? В мануале по ссылке выше наиболее лучшее описание, но всеравно, описано в стиле «ходим, вокруг да около». Хорошо, что у меня был в свое время бэкграунд с изучением асма и я до этого допер сам. Неужели сложно ввести сначала понятие об адресации памяти, ведь это несложно. А потом, опираясь только на этом объяснять про указатели?

★★★★★

Последнее исправление: gh0stwizard (всего исправлений: 1)

Ответ на: комментарий от user_id_68054

где size_t ?

Спасибо, исправил. Сейчас все без ворнингов компилится. Я же сказал, что еще не дошел до этого :)

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

могу лишь только сказать что лет 10~20 назад когда я первый раз начал читать учебник по Си — и там вдруг появилась глава про указатели — то для меня это выглядело примерно так:

http://s00.yaplakal.com/pics/pics_original/4/4/4/282444.jpg

user_id_68054 ★★★★★
()

Почему везде об этом не говорится по-человечески?

Я почитал твоё «по-человечески», это, блджад, каша какая-то.

Суммирование этого адреса с числом означает, автоматическое умножение числа на размер типа данных массива. Для int ages[], ages + i означает адрес ages + i * sizeof(int).

Мысль правильная, но написал ты ересь. ages + i и ages + i * sizeof(int) это две разные вещи.

но ведь в одном случае нам вталкивают что это «массивы», а во втором, что это ссылки на эти массивы.

В первом случае резервируется память под содержимое массивов и указатели. Во втором только под указатели.

а *(names + i) на самом деле просто разыменовывает указатель на эту статичную строку и возращает ее значение.

он возвращает не «значение строки», а указатель на массив из char'ов.

names указывает на участок памяти, где находятся указатели, которые в свою очередь указывают на участки памяти, где находятся char'ы

Остальное не распарсил

anonymous
()
Ответ на: комментарий от user_id_68054

А правильно ли я понял, что в си/си++ нет возможности узнать количество элементов массива по указателю? Т.е. надо для всех функций надо еще вдобавок передавать предварительно расчитанное кол-во элементов (аналогично main(int argc, char * argv[])). Т.к. функция ниже имеет совсем иной смысл:

void process_array(char * names[], int * ages) {
  // result is actually weird :)
  int count_names = sizeof(names) / sizeof(char);
  int count_ages = sizeof(ages) / sizeof(int)
}

int main(int argc, char * argv[]) {
   char * names[] = { "One", "Two", "Three" };
   int ages[] = { 1, 2, 3, 4, 5 };

   // as you expected
   int count_names = sizeof(names) / sizeof(char);
   int count_ages = sizeof(ages) / sizeof(int);

   process_array(names, ages);
}

Ссылаюсь на:

1. http://stackoverflow.com/questions/492384/how-to-find-the-sizeofa-pointer-poi...

2. http://stackoverflow.com/questions/8572158/size-of-array-of-pointers

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Мысль правильная, но написал ты ересь. ages + i и ages + i * sizeof(int) это две разные вещи.

Да. Недорасписал, что калькуляцию ages + i * sizeof(int) производит автоматом компилятор.

он возвращает не «значение строки», а указатель на массив из char'ов.
names указывает на участок памяти, где находятся указатели, которые в свою очередь указывают на участки памяти, где находятся char'ы

Хотел возразить, но ты прав! Что за недокументированные возможнности?!

#include <stdio.h>

int main(int argc, char * argv[])
{
    char text_1[] = "one";
    char text_2[] = "two";
    char text_3[] = "three";

    char * refs[3] = {
        text_1,
        text_2,
        text_3
    };

    printf("%p vs %p (%p wtf %s)\n",
        text_1, refs + 0, *(refs + 0), *(refs + 0));

    printf("%p vs %p (%p wtf %s)\n",
        text_2, refs + 1, *(refs + 1), *(refs + 1));

    printf("%p vs %p (%p wtf %s)\n",
        text_3, refs + 2, *(refs + 2), *(refs + 2));

    return 0;
}

gh0stwizard ★★★★★
() автор топика

наброшу профилактически

#include <stdio.h>

char *eq(void *a, void *b)
{
	return a == b ? "==" : "!=";
}

int main()
{
	char *p = "aaa";
	char a[] = "bbb";

	printf("p: %p, &p: %p, a: %p, &a:%p\n", p, &p, a, &a);

	printf("p %s &p\n", eq(p, &p));
	printf("a %s &a\n", eq(a, &a));
}
./arr
p: 0x8568, &p: 0xbe8db92c, a: 0xbe8db930, &a:0xbe8db930
p != &p
a == &a
AptGet ★★★
()
Ответ на: комментарий от gh0stwizard

Согласно доке тут printf() для %s и %p оперирует указателями char * и void * соответственно. Собственно поэтому такой вывод, хотя на деле *(refs + 2) это всегда ссылка на строковый литерал. Для char производится разыменовывание, для void - нет. Если я правильно все понял.

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от AptGet

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

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

Хотел возразить, но ты прав! Что за недокументированные возможнности?!

Никаких особых возможностей.
text_1 имеет тип char*, он же строка
refs имеет тип char**, он же указатель на строку
refs + 1 имеет тип char**, он же указатель на строку
*(refs + 1) имеет тип char*, он же строка
%s работает со строкой, т.е. с char*

Если я, конечно, не ошибаюсь. Хотя вроде не должен.

proud_anon ★★★★★
()
Ответ на: комментарий от gh0stwizard

ссылка на строковый литерал

Почему литерал? Литерал — константа в исходном коде. Указателю всё равно, откуда взялась строка, была ли она в исходнике или сконструирована в памяти.

Для char производится разыменовывание, для void - нет.

Дело не в разнице между char* и void*, а в разнице между %s и %p.

proud_anon ★★★★★
()
Ответ на: комментарий от gh0stwizard

http://c-faq.com

А по моментам выше — из указателя на первый элемент массива размер массива узнать нельзя, правильно. Либо рядом таскаешь, либо 0-терминатед (если диапазон позволяет, можно и -1-терминатед, главное признак есть и известен), либо и то, и то. Для массива без указания количества размер берется из инициализатора, причем для строкового литерала «abc» размер будет 4, т.к. завершающий '\0'. Про плюс не скажу, никогда такие массивы не складывал. Ну и вместо int a[]; sizeof(a) / sizeof(int) лучше #define NELEMS(a) (sizeof(a) / sizeof((a)[0])) — так не придется при смене типа копаться в сорце.

arturpub ★★
()
Ответ на: комментарий от arturpub

Спасибо за линк, почитаю. Макрос для array_size я уже нашел и использую по мере необходимости.

gh0stwizard ★★★★★
() автор топика

Ну и простыню ты накатал!

Честно говоря, не понимаю, что сложного может быть в понимании указателей? Мне казалось, что у K&R отлично все объяснено.

Кстати, советую еще с указателями на функции побаловаться. А потом попробовать все это (преобразование типов через указатели, указатели на функции и т.п.) реализовать в каком-нибудь недоязычке типа пытона.

Anon
()
Ответ на: комментарий от gh0stwizard

Собственно поэтому такой вывод, хотя на деле *(refs + 2) это всегда ссылка на строковый литерал.

В сях нет такого, что вот мы что-то разыменовали и «ага! это же тот самый литерал!». Вся суть в спецификаторе типа (или просто в «типе»), и операция разыменования *, как и взятия адреса &, это операция над спецификатором. Что там изначально валялось по этому адресу, прежде чем ты автокастом наиболее точный тип потерял — не важно.

arturpub ★★
()
Ответ на: комментарий от Anon

А шо, вроде сишечка, а вроде и лисп. Оргазм ъ-кодера.

arturpub ★★
()
Ответ на: комментарий от Anon

Честно говоря, не понимаю, что сложного может быть в понимании указателей? Мне казалось, что у K&R отлично все объяснено.

Когда я начинал с K&R (русской версии) у меня примеры не получались, собственно поэтому и забросил. Вообще, мне знакомый сишнек после того, как его я пару раз задолбал вопросами про опечатки, сказал, что K&R это не та книга, с которой надо начинать изучать Си. Сейчас вот пошел по hardway, все получается. Просто мануалов по Си полно в сети, только везде как-то отрывочно. Почитал c-fags.com, там вопросы те же самые, но теперь я хоть знаю, в чем разница между char *p = "string" vs char arr[] = "string".

Кстати, советую еще с указателями на функции побаловаться.

Побаловался, вопросов прибавилось :) Но c-fag все объяснил. Офигенный ресурс.

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

Да, теперь я понимаю насколько мощен перл :)

gh0stwizard ★★★★★
() автор топика

много букоф, ниасилил, однако от себя могу сказать, что без бекграунда с асмом тоже никогда не въехал бы в указатели.

IvanR ★★★
()
Ответ на: комментарий от gh0stwizard

c-fag

Ты это осторожней, пропаганду-то запретили ;)

arturpub ★★
()
Ответ на: комментарий от gh0stwizard

си/си++ нет возможности узнать количество элементов массива по указателю

значение sizeof — вычисляется во время компилирования программы (а не во время выполнения программы).

узнать колличество элементов можно только если зарание его где-то запомнить.

а только лишь по указателю — узнать количество элементов нельзя [официально].

int count_names = sizeof(names) / sizeof(char);

этот частный случай работает только лишь потому что компилятор знает кое-что про names. но если бы этот names был бы внутри аргумента функции — то это бы не сработало бы.

вобщем не стоит на это надеется :) .. а стоит всегда полагать что нельзя узнать колличество элементов (только лишь по указателю).

user_id_68054 ★★★★★
()
Последнее исправление: user_id_68054 (всего исправлений: 2)

тебе щас местные аналитики понаговорят (любят тут на пустом месте демагорию разводить). в довесок к k&r читай peter van der linden. deep c secrets. в ней очень хорошо указатели разбираются. по ней в свое время и осилил.

vvviperrr ★★★★★
()
Ответ на: комментарий от user_id_68054

значение sizeof — вычисляется во время компилирования программы (а не во время выполнения программы).

sizeof может вычисляться как в компайл тайме, так и в рантайме. man VLA

vvviperrr ★★★★★
()

Таким образом names это сокращение на *(names + i). Объясняется очень просто: массив в си это список, разделенный на блоки указанного размера, при этом обращение напрямую по имени переменной означает на начальный адресс в памяти. Суммирование этого адреса с числом означает, автоматическое умножение числа на размер типа данных массива. Для int ages[], ages + i означает адрес ages + i * sizeof(int). Именно поэтому cur_name, cur_age по смыслу это тоже самое что и names, ages, но ведь в одном случае нам вталкивают что это «массивы», а во втором, что это ссылки на эти массивы. Но на деле оба являются ссылками, или адресами на участки памяти.

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

Твоя ошибка в том, что указатели и их арифметика — это несколько другое. Есть сущность «массив», а есть сущность «указатель на начало массива». Эти сущности совпадают только численно. Численно это один и тот же адрес в памяти. Но на самом деле, это разные вещи.

Фишка ещё и в том, что массив автоматически преобразуется в указатель. Потому их постоянно путают новички.

Все верно? Почему везде об этом не говорится по-человечески? В мануале по ссылке выше наиболее лучшее описание, но всеравно, описано в стиле «ходим, вокруг да около». Хорошо, что у меня был в свое время бэкграунд с изучением асма и я до этого допер сам. Неужели сложно ввести сначала понятие об адресации памяти, ведь это несложно. А потом, опираясь только на этом объяснять про указатели?

потому-что ты и сейчас всё неправильно понимаешь. На самом деле есть три сущности:

1. массив

2. указатель на голову массива

3. адрес в памяти этого массива

Эти три сущности равны между собой численно, но тем не менее:

1. массив имеет не только тип, но и размер, т.е. число его эл-тов. Этим массив отличается от обычных типов (e.g. int, int*, etc)

2. указатель на массив не имеет размера массива. «Не знает». Что не мешает стандарту запрещать этому указателю выходить за границу массива (которую указатель не знает). Именно потому приходится передавать в функции не только массив, но и его размер, пусть даже неявно, как ASCIIZ строки. Но указатель знает размер одного эл-та. (если он не void*).

3. сущность «адрес» вообще не определена в сишечке.

emulek
()
Ответ на: комментарий от gh0stwizard

Я же сказал, что еще не дошел до этого

сразу скажу, что ЧИСЛО элементов — действительно size_t (т.е. величина «безразмерная», «штуки»)

А вот РАЗНОСТЬ указателей — это ptrdiff_t. Ни в коем случае не перепутай!

emulek
()
Ответ на: комментарий от gh0stwizard

А правильно ли я понял, что в си/си++ нет возможности узнать количество элементов массива по указателю? Т.е. надо для всех функций надо еще вдобавок передавать предварительно расчитанное кол-во элементов

это ты понял правильно. Возможности нет узнать. Но и выйти за границы ты права не имеешь. Иначе UB.

emulek
()
Ответ на: комментарий от gh0stwizard

i[names]

А это где описано?

тебе-же сказали, что это *(i+names). Про переместительный закон слышал?

emulek
()
Ответ на: комментарий от proud_anon

refs имеет тип char**, он же указатель на строку

Если я, конечно, не ошибаюсь. Хотя вроде не должен.

ошибаешься. refs это массив, а НЕ указатель. Если не веришь, посчитай его sizeof.

emulek
()
    // safely get the size of ages
    size_t count = sizeof(ages) / sizeof(ages[0]);

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

bormant ★★★★★
()
Последнее исправление: bormant (всего исправлений: 1)
Ответ на: комментарий от gh0stwizard

Да, теперь я понимаю насколько мощен перл :)

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

fenris ★★★★★
()
Ответ на: комментарий от emulek

ошибаешься. refs это массив, а НЕ указатель. Если не веришь, посчитай его sizeof.

Ах, блин...
Ну, я хотя бы правильно думаю, что единственные две операции над массивом, которые не приводят к скрытому разыменованию указателя на первый элемент, это sizeof (вернет размер массива в байтах) и & (вернет тип «указатель на массив»)?

Да, пляски со строгостью типов в Go и unsafe.Pointer задалбывают только до тех пор, пока не вспомнишь эти имплицитные конверсии в Сях.

proud_anon ★★★★★
()
Ответ на: комментарий от proud_anon

Ну, я хотя бы правильно думаю, что единственные две операции над массивом, которые не приводят к скрытому разыменованию указателя на первый элемент, это sizeof (вернет размер массива в байтах) и & (вернет тип «указатель на массив»)?

вроде да...

задалбывают только до тех пор, пока не вспомнишь эти имплицитные конверсии в Сях.

в сишечки массивы несколько странные, согласен.

emulek
()
Ответ на: комментарий от proud_anon

Массив и указатель — одно и то же. Все отличие в том, что препроцессор знает размер массива с фиксированным размером (если ты не пользуешься новомодными фишками C99) и поэтому sizeof этого массива вернет не размер указателя, а полный размер памяти (за исключением выравнивания), занимаемый этим массивом. А в остальном пофиг: если у нас есть type a[size] и type *b = a, то &a[0], a &0[a] и b — совершенно один и тот же указатель.

И пусть emulek по этому поводу свое мнение выскажет.

Anon
()
Ответ на: комментарий от Anon

Массив и указатель — одно и то же.

Да как вы уже достали... Массив и указатель - не одно и тоже. Потому что семантика отличается. Потому что указатель - это переменная, и у нее есть собственный адрес, а у массива - адрес ВНЕЗАПНЫЙ (см мой пост выше). Да, тип «массив» _почти_ во всех выражениях конвертируется в тип «указатель», но не во всех, не надо их путать.

AptGet ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.