При работе с указателями вы должны твердо держать перед собой следующие две точки:
# 1 Сам указатель отделен от данных, на которые он указывает . Указатель - это просто число. Число говорит нам, где в памяти мы можем найти начало некоторого другого фрагмента данных. Указатель может использоваться для доступа к данным, которые он указывает на , но мы также можем манипулировать значением самого указателя . Когда мы увеличиваем (или уменьшаем) значение самого указателя, мы перемещаем «назначение» указателя вперед (или назад) от точки, на которую он первоначально указывал. Это подводит нас ко второму пункту ...
# 2 Каждая переменная-указатель имеет тип , который указывает, какие данные указывают на . A char *
указывает на char
; int *
указывает на int
; и так далее. Указатель может даже указывать на другой указатель (char **
). Тип важен, потому что, когда компилятор применяет арифметические операции к значению указателя, он автоматически учитывает размер типа данных, на который указывает. Это позволяет нам работать с массивами, используя простую арифметику указателей:
int *ip = {1,2,3,4};
assert( *ip == 1 ); // true
ip += 2; // adds 4-bytes to the original value of ip
// (2*sizeof(int)) => (2*2) => (4 bytes)
assert(*ip == 3); // true
Это работает, потому что массив - это просто список идентичных элементов (в данном случае int
s), последовательно расположенных в одном непрерывном блоке памяти. Указатель начинает указывать на первый элемент в массиве. Арифметика указателя позволяет нам продвигать указатель через массив, элемент за элементом. Это работает для указателей любого типа (за исключением того, что арифметика не разрешена на void *
).
На самом деле, именно так компилятор переводит использование оператора индексатора массива []
. Это буквально сокращение от добавления указателя с помощью оператора разыменования.
assert( ip[2] == *(ip+2) ); // true
Итак, как все это связано с вашим вопросом?
Вот ваши настройки ...
char *pc = "abcd";
char **ppc = &pc;
char **ppc2 = &pc;
на данный момент я упростила удаление вызова на setStaticAndDynamicPointers
. (В этой функции тоже есть проблема - пожалуйста, смотрите ответ @ Nim и мой комментарий там, чтобы узнать больше о функции).
char c;
c = (*ppc)[1];
assert(c == 'b'); // assertion doesn't fail.
Это работает, потому что (*ppc)
говорит "дай мне все, на что ppc
указывает". Это эквивалент ppc[0]
. Это все совершенно верно.
memcpy(&c,&(*ppc[1]),1);
if(c!='b')
puts("memcpy didn't work."); // this gets printed out.
Проблемная часть, как отмечали другие, это &(*ppc[1])
, что буквально означает «дай мне указатель на то, на что указывает ppc [1]».
Прежде всего, давайте упростим ... приоритет оператора говорит, что: &(*ppc[1])
совпадает с &*ppc[1]
. Тогда &
и *
являются инверсиями и отменяют друг друга. Так &(*ppc[1])
упрощается до ppc[1]
.
Теперь, учитывая вышеприведенное обсуждение, мы теперь готовы понять , почему это не работает: Короче говоря, мы рассматриваем ppc
, как будто он указывает на массив указателей, когда на самом деле он указывает только на один указатель.
Когда компилятор встречает ppc[1]
, он применяет арифметику указателей, описанную выше, и создает указатель на память, которая следует сразу за переменной pc
- независимо от того, что эта память может содержать. (Поведение здесь всегда не определено).
Так что проблема вовсе не в memcopy()
. Ваш вызов memcpy(&c,&(*ppc[1]),1)
покорно копирует 1 байт (согласно запросу) из памяти, на которую указывает фальшивый указатель ppc[1]
, и записывает ее в символьную переменную c
.
Как уже отмечали другие, вы можете исправить это, переместив круглые скобки:
memcpy(&c,&((*ppc)[1]),1)
Надеюсь, объяснение было полезным. Удачи!