Разница между динамически создаваемым массивом (int * arr) и статически созданным массивом (int arr []) при поиске размера массива с использованием арифметики с указателями - PullRequest
3 голосов
/ 07 апреля 2020
#include <stdio.h>

int main(void)
{
    int arr[] = { 1, 2, 3, 4, 5 };

    //printf("The size of the array is %d", n); //assuming int 4 bytes
    printf("%p - %p: %d\n",(&arr)[1], arr, (&arr)[1] - arr);

    return 0;
}

Я знаю, что (&arr)[1] даст адрес следующего блока памяти (адрес памяти после последнего элемента массива), а arr будет содержать адрес первого элемента в массиве. Следовательно, разница даст количество байтов между ними. Приведенный выше код дает размер массива, который он должен делать.

Но я не знаю, почему разница наступает /4. Я думал, что это потому, что мы используем %d. Я пробовал разные спецификаторы формата, но ничего не получалось. Ответ на этот вопрос вторичен.

Теперь я попробовал то же самое, используя динамическое выделение памяти c, как показано ниже:

#include<stdio.h>
#include<stdlib.h>

int main(){
    int *arr, i;
    arr = (int*)malloc(10*sizeof(int));
    for (i = 0; i < 10; i++){
        // scanf("%d", &arr[i]);
        arr[i] = i;
    }

    printf("%p - %p: %d\n",(&arr)[1], arr, (&arr)[1] - arr);
    return 0;
}

Но результат другой. Это дало случайное значение.

Тогда я попытался:

#include<stdio.h>
#include<stdlib.h>

int main(){
    int *arr, *ptr, i;
    arr = (int*)malloc(10*sizeof(int));
    ptr = arr;
    for (i = 0; i < 10; i++){
        // scanf("%d", &ptr[i]);
        ptr[i]=i;
    }

    printf("%p - %p: %d\n",(&ptr)[1], ptr, (&ptr)[1] - ptr);
    return 0;
}

Результат - 0. Конечно, я пробовал разные возможные комбинации, чтобы использовать ptr и arr в выражении print и for loop. Все дало 0. Очевидная причина в том, что один и тот же адрес для обоих.

Мое предположение для такого поведения связано с различием между динамически созданным массивом и статически созданным массивом.

Может кто-нибудь объяснить мне, в чем причина?

Заранее спасибо. Пожалуйста, помогите мне исправить себя, если я спрашиваю что-то не так.

Редактировать: Чтобы сделать вопрос более ясным, я изменил %d на %p, где это требуется в соответствии с предложениями сообщества. Ура!

Ответы [ 3 ]

1 голос
/ 08 апреля 2020

Массивы

Вы смешиваете arrays и pointers, которые представляют собой два разных объекта. Давайте проведем мысленный эксперимент:

Представьте, что у каждого объекта в C есть адрес в памяти. Поэтому объявление типа int a; зарезервирует адрес памяти для переменной a. Этот адрес памяти можно увидеть с помощью &a, например:

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

Массивы - это объекты, которые содержат последовательности данных одного типа. Следовательно, массив int имеет последовательность int s в памяти. Адрес массива - это адрес первого элемента (поэтому вы можете получить доступ к массиву и разыменовать его).

Именно поэтому вы получаете доступ к первому элементу массива с помощью array[0] вместо array[1]. Индекс представляет собой смещение от базового адреса, который задается именем array, указывающим на первый элемент в памяти.

 arr[0]
 |       arr[2]
 |       |
 v       v
 +---+---+---+---+---+
 | 0 | 1 | 2 | 3 | 4 |
 +---+---+---+---+---+

Возвращаясь к вашей проблеме, в время компиляции компилятор знает размер вашего массива, который равен * c. В вашем коде это 5 элементов. Элементы имеют тип int в вашем случае. Следовательно, размер вашего массива составляет 5 elements * 4 bytes of unit size = 20 bytes (учитывая, что ваша реализация C int имеет длину 4 байта).

Таким образом, вы можете делать с ним все виды трюков, потому что компилятор знает размер массива. Таким образом, вы можете создавать макросы типа SZ(n) (sizeof(n)/sizeof(n[0])), чтобы получить размер массива в элементах. Код для проверки:

/* so1.c
 */

#include <stdio.h>

#define SZ(n)       (sizeof(n)/sizeof(n[0]))

int main(void)
{
    int arr[] = {1, 2, 3, 4, 5};

    printf("Array size (byges)   : %d\n", sizeof(arr));
    printf("Array size (elements): %d\n", SZ(arr));

    printf("Address of arr      : %p\n", arr);
    printf("Address of &arr     : %p\n", &arr);
    printf("Address of arr[0]   : %p\n", &arr[0]);
    printf("Address of &arr[4]  : %p\n", &arr[4]);
    printf("Address of (&arr)[1]: %p\n", (&arr)[1]);

    printf("arr[4] - arr[0]     : %d\n", arr[4] - arr[0]);

    return 0;
}

Обратите внимание, что разница в смещении адресов между arr[4] и arr[0] составляет 16 bytes или 4 int элементов, каждый размером 4 байта. Вы можете задаться вопросом, почему это так, поскольку размер массива составляет 20 байтов, но учтите, что последний элемент начинается со смещением 16 байтов от начала массива и охватывает до конца массив (последние 4 байта будут использоваться последним элементом).

Указатель на массивы и Address-of-Operator

Есть также кое-что очень важное для рассмотрения. Учитывая свойства оператора &, когда вы приводите его в массив типа T [n], он становится указателем на тип T(*)[n], который оценивается по-разному, и вы должны прочитать этот ответ в поймите разницу.

(T [n]) + 1   => address of T + 1 byte
(T(*)[n]) + 1 => address of T + (n * 1) bytes

И это причина, по которой трюки типа (&arr[1])-1 будут указывать на последний элемент в массиве в большинстве реализаций, но это не переносимо и не не соответствует стандарту - поэтому не рекомендуется практиковать.


Указатели

Теперь рассмотрим следующую программу, которая использует динамическую память c:

/* so2.c
 */

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *p;
    int i;

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

    if(!(p = malloc(5 * sizeof *p))) {
        perror("malloc");
        exit(-1);
    }

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

    for(i = 0; i < 5; i++) {
        p[i] = i + 1;
    }

    printf("&p[0]: %p\n", &p[0]);
    printf("&p[4]: %p\n", &p[4]);

    free(p);
    return 0;
}

Перед malloc вы можете увидеть значение из pointer *p, равное NULL. Это означает, что это никуда не указывает. Однако адрес pointer существует (&p), потому что мы должны где-то его хранить. После вызова malloc вы видите, что p теперь имеет значение. Это значение является адресом блока памяти , который нам дала система.

Компилятор не может узнать размер этого фрагмента памяти во время компиляции (как он узнает адрес чего-то, что операционная система только что дала нам на лету ?! ). На самом деле, размер может сильно отличаться от того, который вы запрашиваете. malloc(n) и его братья и сестры гарантируют, что возвращенный кусок памяти будет достаточным для размещения по крайней мере n bytes содержимого, но не гарантирует размер фрагмента (или то, что память доступна!). Из mallo c (3), ручного :

По умолчанию Linux следует оптимизированной стратегии выделения памяти c. Это означает, что когда mallo c () возвращает не NULL, нет гарантии, что память действительно доступна. Если окажется, что в системе недостаточно памяти, убийца OOM уничтожит один или несколько процессов. Для получения дополнительной информации см. Описание / proc / sys / vm / overcommit_memory и / proc / sys / vm / oom_adj в pro c (5) и исходный файл ядра Linux Documentation / vm / overcommit- accounting. первый.

Поэтому нет способа узнать, какой это размер.

Sizeof (Pointer)

Когда вы вызываете sizeof для указателя, он просто возвращает размер указателя в заданная архитектура, которая будет 4 байта на 32-битной машине и 8 байтов на 64-битной машине. И когда вы приводите оператор address-of & к указателю, он возвращает указатель на указатель (T (*)(*)), который, тем не менее, является указателем и имеет одинаковый размер обычного указателя T(*) - потому что оба теоретически будет хранить адрес памяти.

Предупреждение : Я знаю, что говорить об адресе памяти слишком упрощенно, потому что стандарт языка C не поговорим о специфике реализаций. Тем не менее, это действительно помогает сначала подумать об этом низком уровне, а затем пробираться через мелочи и идиосинкразии стандарта. Глава о указателях K & R C - лучший справочник, который вы можете себе представить - прочитайте его и выполните упражнения.

1 голос
/ 08 апреля 2020

Объяснение наблюдаемых результатов

Результаты с массивами

После int arr[] = { 1, 2, 3, 4, 5 };, arr - это массив из пяти int.

Затем &arr является указателем на массив из пяти int, а (&arr)[1] будет массивом из пяти int после arr, если бы он был. В целях объяснения результатов, которые вы видели, давайте предположим, что на данный момент они есть. (Ниже я объясню без этого предположения.)

Как массив, (&arr)[1] автоматически преобразуется в указатель на его первый элемент. 1 Так что (&arr)[1] действует как указатель к первому int в массиве из пяти int, который следует arr в памяти.

Аналогично, поскольку arr является массивом из пяти int, он преобразуется в указатель на его первый элемент. Так что arr действует как указатель на первый int в нем.

Когда вы печатаете их с помощью %d, программа может напечатать адрес памяти, который является значением указателя, или часть Это. (%d - неверный спецификатор преобразования для использования. См. Ниже.) Если это так, вы увидите фактические адреса как необработанные адреса памяти, обычно измеряемые в байтах.

В (&arr)[1] - arr вы вычтете эти два указатели. Когда вы вычитаете два указателя в C, результатом будет количество элементов массива между двумя расположениями. Это не количество байтов. Стандарт C требует, чтобы реализация C предоставляла результат в виде числа элементов, даже если для выполнения преобразования из байтов в элементы массива необходимо выполнить деление.

Начиная с (&arr)[1] (после автоматизации c преобразование) указывает на первые int в массиве после массива из пяти int, то есть arr, а arr (после преобразования) указывает на первые int в arr, они отличаются на пять int, и поэтому результат равен пяти. Это то, что вы видели напечатанным, хотя вы должны использовать %td для печати результата вычитания указателя, а не %d.

Результаты с указателями

После int *arr; arr = (int*)malloc(10*sizeof(int));, arr это указатель на int. Тогда &arr является указателем на этот указатель, а (&arr)[1] будет указателем после arr, если бы он был. Когда вы печатаете необработанный адрес памяти arr, вы увидите значение, возвращаемое malloc. Однако, когда вы печатаете (&arr)[1], мы не знаем, что вы увидите - после arr нет указателя, и ваша реализация C может вывести любое значение в памяти после arr, но мы не знаем что это такое И, поскольку мы не знаем, каким будет значение (&arr)[1], мы не знаем, каким будет значение (&arr)[1] - arr.

В вашем случае ptr = arr; то же, что и выше, верно - нет правильного (&ptr)[1], поэтому мы не знаем, что будет напечатано. Возможная причина того, что «0» была напечатана, когда вы пытались это сделать, заключается в том, что компилятор поместил arr в память сразу после ptr, поэтому (&ptr)[1] было arr, а затем (&ptr)[1] - ptr равно arr - ptr, и это ноль, поскольку вы устанавливаете ptr равным arr.

Объяснение того, что говорит C Стандарт и исправление кода

Правильное использование указателей и обращение к объектам

Как указано выше, (&arr)[1] относится к массиву из пяти int после arr, но такой массив не был определен. Из-за этого поведение (&arr)[1] не определяется стандартом C. Следовательно, поведение printf("%d - %d: %d\n",(&arr)[1], arr, (&arr)[1] - arr); не определяется стандартом C.

Вместо этого вы можете использовать (&arr + 1). Это указывает «один за пределы» массива arr. То есть, он указывает, где будет следующий массив из пяти int, если таковой будет. То же самое место было бы (&arr)[1], но (&arr+1) определено, потому что выполнение указателя arithmeti c до «чуть дальше» объекта определяется стандартом C. (&arr)[1] не определен, поскольку он не просто выполняет арифметику указателей c, но технически является ссылкой на несуществующий объект - технически это использование объекта, который не существует, даже если это немедленно преобразовано в указатель. Арифметика указателя c сразу после определения объекта, но использование гипотетического объекта сразу после определения одного объекта.

Другая альтернатива - &(&arr)[1]. Это берет адрес (&arr)[1], который все еще будет неправильной ссылкой на объект, который не существует, за исключением того, что определение & таково, что оно отменяет *, который неявно присутствует в операторе индекса. Поэтому &(&arr)[1] определено как (&arr + 1), хотя (&arr)[1] не определено.

Правильные преобразования Printf

Чтобы напечатать указатель p, используйте printf("%p", (void *) p);.

Чтобы напечатать результат вычитания указателей p и q, используйте printf("%td", p-q);.

Итак, правильный printf для вашего первого случая может быть:

printf("%p - %p: %td\n", (void *) (&arr+1), (void *) &arr, (&arr+1) - &arr);

или:

printf("%p - %p: %td\n", (void *) (arr+5), (void *) arr, (arr+5) - arr);

В первом из них будут напечатаны адреса двух массивов и разница между ними в единицах массивов по пять int. Эта разница будет одна.

Вторая напечатает адрес int сразу за массивом arr и адрес первого int в arr и разницу между ними в единицах int. Эта разница будет одна. Два адреса в этом printf будут такими же, как адреса в первом printf, потому что они указывают на одно и то же место. (Примечание: стандарт C позволяет реализациям C иметь несколько способов представления указателей, поэтому возможно, что адреса при печати будут отображаться по-разному. Однако в большинстве распространенных реализаций C они будут кажутся идентичными.)

Ваш второй и третий случаи не могут быть легко исправлены, потому что они оба полагаются на использование значения объекта вне определенного единственного объекта (указатель). Мы могли бы исправить первый случай, потому что он использует только адрес объекта за пределами определенного объекта, и есть способы использовать этот адрес определенным образом. Поскольку во втором и третьем случаях делается попытка использовать значение несуществующего объекта, а не только его адрес, они изначально не определены.

Сноска

1 При использовании в выражении любой массив автоматически преобразуется в указатель на его первый элемент, за исключением случаев, когда он является операндом sizeof, операндом унарного & или строковым литералом, используемым для инициализации массива. Это преобразование происходит независимо от того, указан ли массив как arr или как результат выражения как (&arr)[1].

0 голосов
/ 08 апреля 2020

Сначала мы должны понять некоторые понятия

первое - mallo c оно исходит из выделения памяти , поэтому вы резервируете память, но за что? для массива массив - это блок непрерывной памяти, как сказал PJ, в вашем случае вы резервируете 10 пробелов памяти (10 * размер целого) = десять пробелов для целых чисел

Второй - (*) указатель (оператор разыменования) и оператор Address-of (&): указатель может использоваться для доступа к переменной, они указывают непосредственно.

если вы объявляете переменную

myvar == 25 ; & myvar == (адрес памяти - случайное число)

в вашем случае у вас есть адрес памяти, и вы не обращаете внимания на то, что это за адрес, вы хотите, чтобы содержался этот адрес памяти, так что с * (ваша переменная, которая содержит адрес, к которому вы хотите получить доступ), вы можете достичь значения

foo == 1776 (случайное число) * foo == 25 (вы назначаете этому адресу памяти значение 25)

вот документация, которая помогает http://cplusplus.com/doc/tutorial/pointers/

счастливого кодирования! :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...