Размер массива без оператора sizeof
Я пытаюсь понять программу ниже, но мне это не понятно.
#include<stdio.h>
int main()
{
int a[]={1,2,3,4,5,6,9};
printf("sizeof array is %d\n",sizeof(a));
printf("size of array using logic is %d\n",((&a)[1]-a));
printf("value of (&a)[1] is %p \n",(&a)[1]);
printf("value of a is %p \n",a);
printf("address of a[0] is %p\n",&a[0]);
printf("address of a[1] is %p\n",&a[1]);
printf("address of a[2] is %p\n",&a[2]);
printf("address of a[3] is %p\n",&a[3]);
printf("address of a[4] is %p\n",&a[4]);
printf("address of a[5] is %p\n",&a[5]);
printf("address of a[6] is %p\n",&a[6]);
}
Выше вывод кода:
sizeof array is 28
size of array using logic is 7
value of (&a)[1] is 0x7ffc4888e78c
value of a is 0x7ffc4888e770
address of a[0] is 0x7ffc4888e770
address of a[1] is 0x7ffc4888e774
address of a[2] is 0x7ffc4888e778
address of a[3] is 0x7ffc4888e77c
address of a[4] is 0x7ffc4888e780
address of a[5] is 0x7ffc4888e784
address of a[6] is 0x7ffc4888e788
Мне непонятно, почему ((&a)[1]-a))
во второй оператор печати возвращается 7; он должен быть 0x7ffc4888e78c - 0x7ffc4888e770
, который равен 0x1c
i.e 28 общий размер массива.
Для справки я также попробовал распечатать (&a)[1]
и значения, которые вы можете увидеть в коде. Я также пробовал отлаживать.
Ответы
Ответ 1
Таким образом, указатели не являются целыми числами. Конечно, вы можете преобразовать их в целые числа, переведя их в целочисленный тип или добавив к ним целые числа, чтобы сместить их. Но они не являются целыми числами.
Указатели похожи на математические векторы над целыми числами, если вы сделали какую-либо линейную алгебру.
p1-p2
- это расстояние между p1
и p2
, целое число, необходимое для добавления к p2
для достижения p1
.
Когда вы добавляете целое число в указатель, вы должны обратить внимание на тип указателя. Если указатель относится к объекту размером 4, каждый раз, когда вы добавляете 1 к указателю, его числовой адрес увеличивается на 4, а не на 1.
То же самое верно, когда вы вычитаете два указателя.
Ключевая часть здесь заключается в том, что числовое значение адреса в памяти имеет значение, но этот тип имеет такое же значение, чтобы понять, что происходит.
Вторая странная вещь, происходящая здесь, заключается в том, что массивы распадаются на указатели на их первый элемент при падении шляпы. Они, однако, не являются указателями на их первый элемент, они просто легко конвертируются в них.
Итак, когда мы это делаем:
(&a)[1]
мы берем адрес a
. Адрес a
является указателем типа int(*)[7]
. Это указатель на массив, а не указатель на первый элемент массива. Разница заключается в типе указателя. И это важно.
Затем мы используем []
в указателе. Если у вас есть указатель или массив p
и значение v
, p[v]
определяется как *(p+v)
. Это приводит к юмору, если вы делаете v[p]
, но это не важно.
Пусть pa
представляет (&a)
. Тогда pa[1]
будет *(pa + 1)
.
Теперь pa
является указателем на массив (а не является указателем на первый элемент массива). Итак, +1 добавляет полный размер массива (sizeof (int) * 7) к числовому значению.
So pa+1
является указателем на один конец конца a
и имеет тип-указатель-массив.
Затем мы разыскиваем и получаем несуществующий массив размера 7 сразу после окончания массива a
.
Затем вычитаем a
.
(&a)[1]-a
Здесь происходит разложение указателя. На массивах нет операции -
, но есть указатель -
для указателей. Таким образом, C-язык успешно распаковывает каждый из этих массивов в указатели на их первый элемент.
Указатель на первый элемент a
равен &a[0]
.
Указатель на первый элемент массива размера 7 сразу после окончания a
равен... &a[7]
.
Оба этих указателя имеют тип int*
. Когда вы вычитаете два int*
s, вы получаете их числовое значение указателя, разделенное на sizeof(int)
. В этом случае это легко - 7.
Это может быть проще, если мы посмотрим на это:
(&a)[1]-(&a)[0]
или
*(&a+1)-*(&a+0)
&a
является указателем на массив a
типа "указатель на массив размера 7". Мы добавляем 1 к нему, получая указатель на массив потом в одном случае, и ноль в другом случае.
Затем мы возвращаемся к массивам и вычитаем. Subtraction триггеры распадаются на указатель на первый элемент, поэтому мы получаем указатель на элемент сразу после конца a и указатель на первый элемент a.
&a[7]-&a[0]
который
&*(a+7)-&*(a+0)
Теперь &*
ничего не делает для вещей, которые уже являются указателями (которые они находятся в этой точке), поэтому:
(a+7)-(a+0)
Затем возникает вопрос, сколько вам нужно добавить в a+0
для достижения a+7
. Ответ, что неудивительно, это 7
:
(a+7) = (a+0)+7
и это то, что отображается.
Ответ 2
Если вы выполняете (&a)[1]
и a
до long
перед вычислением, вы получите ожидаемый результат. Как прокомментировал hacce, вы в настоящее время вычисляете разницу указателей.
// These two sizes will be the same
printf("sizeof array is %ld\n",sizeof(a));
printf("size of array using logic is %ld\n",((long)(&a)[1]-(long)a));
Объяснение математики
Что происходит в этом случае, &a
считается типом int(*)[7]
.
Затем вы ссылаетесь на (&a)[1]
, что соответствует *((&a)+1)
. На английском языке это означает "дать мне точку в памяти 1 после начала a
". Поскольку &a
оказывается типом int(*)[7]
, эта точка находится в конце массива.
Когда вы вычитаете a
, указатель на начало массива, вы выполняете арифметику указателя и берете базу размером int
(потому что a
является массивом int
). Таким образом, выражение ((&a)[1]-a)
подсчитывает, сколько int
находится между (&a)[1]
и a
.
Обзор арифметики указателя можно найти здесь.
Ответ 3
(&a)[1]
- адрес ячейки памяти после массива a
, т.е. 0x7ffc4888e788
. (&a)[1]
имеет тип int *
. После преобразования a
будет иметь тип int *
. Это эквивалентно (&a)[0]
.
Стандарт говорит, что:
С11-§6.5.6/9:
Когда два указателя вычитаются, оба должны указывать на элементы одного и того же объекта массива или один за последним элементом объекта массива; результатом является разность индексов двух элементов массива.
Разность (&a)[1]-a
дает количество элементов в массиве a
. Обратите внимание, что a
в этом выражении является адресом элемента массива a[0]
, после распада, и этот адрес эквивалентен адресу массива a
, хотя &a
и a[0]
имеют другой тип.
Вы можете представить эту разницу как (&a)[1]-(&a)[0]
или &a[7] - &a[0]
.
sizeof(a)
дает размер памяти, выделенный для массива a
. sizeof(a)/sizeof(a[0])
даст количество элементов массива a
.
Ответ 4
Вы используете указатель int*
.
Все арифметические операции на нем с использованием 4 bytes
(sizeof(int)
, если быть точным) как единица.
Разница между двумя указателями выражается в этих единицах.
((&a)[1]-a))
равен sizeof(a)/sizeof(a[0])
.
Для вычисления размера массива в байтах вам нужно указать указатель на целочисленное значение, unsigned int:
printf("size of array using logic is %d\n",((int)((&a)[1])-(int)a));
Ответ 5
Если вы хотите получить количество байтов, не используя sizeof
-оператор, а вместо того, чтобы использовать long
тип данных *, я считаю, что более идиоматичным и безопасным способом является включение обоих указателей в char *
:
printf("Size of array using pointer arithmethic is %td.\n", (char*)(&a)[1] - (char*)a);
Результат:
Размер массива с использованием указателя arithmethic равен 28.
Обратите внимание, что спецификатор формата %td
подходит для типа данных ptrdiff_t
(определен в <stddef.h>
), то есть как показано различие указателей.
*) Существуют специальные типы данных intptr_t
и uintptr_t
для представления указателей объектов в виде целых чисел, если вам это действительно нужно.
Ответ 6
В первую очередь спасибо всем и особую благодарность Якку за то, что он дал мне такой отличный анализ на простой указатель arthamatics.I наконец выяснил, почему это происходит, как @Yakk объяснил подробно, что очистил меня в значительной степени, но все еще имел некоторые сомнения на этом, поэтому я начал сменять код и попытался проверить артикулу указателя.
Один короткий ответ - если используется & a [0], он ссылается на первый элемент в адресе массива. Если используются a или a, они относятся к базовому адресу полного массива размера 7. Теперь, чтобы понять, мы использовали (& a) [0], которые указывают на базовый адрес массива размера 7 при увеличении до 1, который переходит в один конец конца массива a. Как объясняется - Yakk, как показано ниже:
Это может быть проще, если мы посмотрим на это:
(& а) [1] - (& а) [0]
или
(& а + 1) - (& а + 0)
& a - указатель на массив a типа "указатель на массив размера 7". Мы добавляем 1 к нему, получая указатель на массив потом в одном случае, и ноль в другом случае.
Затем мы возвращаемся к массивам и вычитаем. Subtraction триггеры распадаются на указатель на первый элемент, поэтому мы получаем указатель на элемент сразу после конца a и указатель на первый элемент a.
& а [7] - & а [0]
который
& (а + 7) - & (а + 0)
Теперь & * ничего не делает для вещей, которые уже являются указателями (которые они находятся в этой точке), поэтому:
(а + 7) - (а + 0)
Затем возникает вопрос, сколько вам нужно добавить к + 0, чтобы достичь + 7. Ответ, что неудивительно, - 7:
(a + 7) = (a + 0) +7
и это то, что отображается.