2015-05-08 2 views
2

программисты!Адреса указателей и арифметических указателей

Я полностью погружен с двойной стрелкой (указатель на указатели) ... Много вопросов здесь!

Начнем с выполнения этой задачи: Я пишу свою собственную версию функции calloc func, которая должна возвращать указатель на 'n' элементы памяти размера 'size'. Вот что я придумал:

void **calloc1(int n, int size) 
{ 
    int i, j; 
    char *tmp, **p = NULL; 
    tmp = (char *) malloc1(n * size); 
    p = tmp; 
    for (i = 0; i < n; i++) { 
     p[i] = tmp; 
     for (j = 0; j < size; j++) 
      tmp++; 
    } 
    p = &p[0]; 
    return (void **)p; 
} 
/* I entered n==4, size==3; real pointers are: p[0] == 0x804cfe0; p[1] == 0x804cfe3; p[2] == 0x804cfe6; ... */ 

Таким образом, по существу, я выделяю п * байт размера, а затем «назначить» массив указателей равного размера «» на соответствующие стартовые позиции. Введем n = 4 и size = 3; это означает, что p [0] указывает на tmp [0], p [1] на tmp [3], p [2] на tmp [6] и т. д. В GDB я отслеживаю значения указателей почти после каждого шага.

Тогда в «основной» Я объявляю двойной указатель и «прикрепить» его буфер получил от моего «calloc»:

int main (short argc, char **argv) 
    { 
     char **space; 
     space = (char **) calloc1(n, size); /* removing '(char**)' here does not have effect */ 

    /* at this stage pointers seems to be correctly "located": 'space' == 'space[0]' == 0x804cfe0; 'space[1]' == 0x804cfe3; 'space[2]' == 0x804cfe6; ... */ 

1) Вот уже первый вопрос: как может «главный () '(или любая другая функция, через которую я передам копию ** p), будет знать размер арифметики указателя? Например, как «main()» знает, что если я добавлю «1» в «пробел» (или просто увеличиваю его один раз), он должен указывать на свой второй указатель (в «calloc» это p [1]), который (в этом конкретном случае) является еще 3 символами первого указателя (p [0])? Кроме того, если я создаю в массиве «alloc» указателей на строки с «переменной длиной» (например, p [0] указывает на tmp [0], p [1] на tmp [7], p [2] на tmp [11] и т. д.), как любая другая функция знает, где она должна увеличивать «верхний» указатель на 4 и где на 7 «символов»?

Хорошо, мы двигаемся дальше, я пытаюсь поставить некоторые символы в приобретаемой буфер:

int i = 0, j = 0, n, size; 
char nn, ssize, c, temp[3]; 

printf ("Enter number/size \n"); 
sc = scanf ("%c/%c", &nn, &ssize); 
n = nn - '0'; /* n==4 */ 
size = ssize - '0'; /* size==3 */ 

printf ("Enter a 'number' of words\n"); 
while (j < n) { 
    for (i = 0; (c = getchar()) != EOF && i < size; i++) 
     *(*space)++ = c; 
    (*space)--; /* this line is unneccesary; if I remove it - nothing changes */ 
    ++j; 
    ++space; 
} 

2) А вот свидетельство на первый вопрос: на самом деле, когда я увеличиваем «пространство» здесь, она движется не на 3, а на 4 символа (после первого «++» это 0x804cfe4, после второго 0x804cfe8). Зачем? Есть ли связь с размером «float»? После первого такого приращения «* space» указывает на 0x804cfe6 ... Я не думаю, что это правильно.

Я попробовал другой путь - ссылается на «пространство», как не указатель, а массив:

.... 
while (j < n) { 
    for (i = 0; (c = getchar()) != EOF && i < size; i++) 
     *space[j]++ = c; 
    space[j]--; 
    ++j; 
} 

3) В этом случае, указатели, кажется, все в порядке - например, space [1] == 0x804cfe3, space [2] == 0x804cfe6. Проблема в том, что, хотя этот цикл работает с j == 2, значение 'space [0]' как-то изменяется от 0x804cfe2 (перемещается дважды - ok) на что-то вроде 0x6a04cfe2 (что выходит за рамки). Что за ..

4) И вообще, существует какое-то странное поведение адресов. Кроме того, я старался не писать символы непосредственно ** пространства, но использовать функцию копирования строки:

char i, temp[3]; 
    ... 
    while (j < n) { 
     for (i = 0; (c = getchar()) != EOF && i < size; i++) 
      temp[i] = c; 
     strncpy1 (space[j],temp,3); 
     ++j; 
    } 
..... 
void strncpy1 (char *s, char *t, int k) 
{ 
    while (--k > 0) { 
     *s = *t; 
     s++; t++; 
    } 
} 

Внутри копирования FUNC, копирование и увеличивающиеся шоу в GDB правильный. Но после возвращения из 'strncpy1', пространство [j] изменяется от 0x804cfe0 до примерно 0x804000a. Как возможно, что вызываемая функция может влиять на указатель родителей (внешний)?

Итак, какой тип указателя-указателя-символа? Какой у него размер?

+2

Я не понимаю, что вы пытаетесь сделать с вашей функцией calloc. Почему он возвращает 'void **' вместо того, чтобы возвращать' void * ', как это делает обычный calloc? – hugomg

+0

Да, я ожидал такого вопроса)) в любом случае, меняя calloc1 с 'void **' на 'void *' (и возвращая тип указателя from '(void **) p' to '(void *) p') не имеет никакого эффекта ... – digitest

+1

Прежде чем делать что-либо еще, нарисуйте карту памяти на бумаге, где расположены ваши распределения памяти, и где указывается каждый указатель. –

ответ

1

Я считаю, что размер вашей системы указателя равен 4, и поскольку вы используете указатель на указатель, а затем вставляете его, чтобы добавить 4 байта в текущее местоположение, чтобы достичь следующего указателя его типа (char **).

ПРИМЕЧАНИЕ: вы не увеличиваете указатель на символ, вы увеличиваете указатель на указатель.

Теперь снова, если я расскажу о вашем втором вопросе о том, как функция будет знать, где увеличить 4 и где 7, поэтому она не связана с функцией. Поскольку массив указателя имеет адрес указателя, который сохраняется в последовательном местоположении (они не являются значением указателя, я говорю о указателях, которые сохраняются в массиве указателя), поэтому он просто увеличит этот указатель на один и достигнет следующего указателя своего типа погода находится на P [0] или P [4] или p [7].

+0

Hi, Dayal! Да, спасибо, я вижу, что я увеличиваю указатель на не char, а на другой указатель. Я думаю, что начинаю понимать ... Итак, «главный» («верхний») указатель здесь на самом деле является последовательной ячейкой памяти, верно? И какова длина этого места? Каков размер одной позиции указателя -1/2/4 байта? – digitest

+0

, так как это массив указателей, поэтому каждое местоположение будет иметь 4 байта. –

1

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

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

Если вы действительно хотите сделать это, вы могли бы сделать это следующим образом:

void **calloc1(int n, int size) 
{ 
    int i, j; 
    char *tmp, **p = NULL; 
    tmp = (char *) malloc1(n * size); 
    p = (char**) malloc1(n * sizeof(char*)); 
... 

ПРИМЕЧАНИЕ: таким образом, вы, естественно, нужно два вызовов, чтобы освободить память, и вы можете не иметь любой способ узнать, что второй, поскольку пользователь может изменить указатели. Таким образом, даже лучше было бы объединить в один распределение:

void **calloc1(int n, int size) 
{ 
    int i, j; 
    char *tmp, **p = NULL; 
    p = (char**) malloc1(n * size + n * sizeof(char*)); 
    tmp = (char *) (p + n); 
... 

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

Что касается других вопросов:

1), как можно 'основной()' знать размер арифметики указателей?

Указатели всегда имеют фиксированный размер. Они указатели, они не заботятся о том, что они указывают. SizeOf (Char *) == SizeOf (INT *) == SizeOf (yourcoolstruct *)

2) когда приращение 'пространства' здесь, она движется не на 3, а на 4-х символов (после того, как первый «++ 'это 0x804cfe4, после второго 0x804cfe8). Зачем? Есть ли связь с размером «float»?

Потому что в вашей системе sizeof (pointer) == 4, поэтому каждый указатель занимает 4 байта. Нормальная в 32-битных средах и не имеет ничего общего с поплавками, ints или чем-то еще.

3) в то время как этот цикл работает с J == 2, значение «пространства [0]» каким-то образом изменяется от 0x804cfe2 (переехал дважды - ок) к чему-то вроде 0x6a04cfe2 (который выходит за границы). Что за ..

Поскольку вы используете ту же память для указателей и данных. Вы пишете 'j' (0x6A в шестнадцатеричном формате) в космос [1] [0], что указывает на четвертый байт распределения.Который также является самым значительным байтом указателя пробела [0], поэтому он становится таковым.

+0

Спасибо, Сами! Вообще-то, я только заканчиваю свое исследование Кернигана-Ричи и завязываюсь с окончательными задачами, имея дело с памятью. Итак, хмм ... Не строки, такие как 'char * tmp, ** p = NULL;' или 'char ** space;' сделать необходимое распределение для этих указателей? Я спрашиваю, потому что через все примеры в книге и во всех своих задачах (которые я успешно выполнил) не было никакого специального выделения памяти для переменных, указателей и т. Д. Они (мы) просто объявляют некоторый указатель, а затем присваивают некоторое значение к нему (например, char * ptr, b [5]; ptr = b;) – digitest

+0

Да, 'char ** p' будет выделять память * для указателя на указатели *, но, конечно, память, содержащая указатели * должен выделяться отдельно. И если эти указатели * указывают на некоторые * данные *, это также должно быть каким-то образом выделено. Если вы используете 'char b [5]', то вы явно выделяете пять символов времени компиляции памяти и, естественно, можете использовать его без каких-либо распределений. –

Смежные вопросы