Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Определение размера массива на этапе компиляции
Форум разработчиков электроники ELECTRONIX.ru > Cистемный уровень проектирования > Операционные системы > Программирование
dxp
Не уверен, в тот ли форум написал - не нашёл специализированного подфорума по программированию, есть этот и ещё в разделе по DSP.

Вопрос чисто по программированию на С/С++.

Задача. Есть энное количество массивов. Тип элемента массива в данном случае не важен (на практике он может быть любым - в частности, у меня это небольшая структура), поэтому в примере будет просто int. Длины массивов произвольные. Обработка данных из массивов возложена на функцию. Вариант на голом С.
CODE
int A[] = { 1, 2, 3 };
int B[] = { 4, 5 };
int C[] = { 6 };
int D[] = { 7, 8, 9, 0 };
...   //  и т.д.

void f(int *p, size_t size) { ... }

вызов (пример):
CODE
f(A, sizeof(A));

Очевидный недостаток - нужно руками задавать правильный размер массива. При достаточно большом количестве массивов и вызовов, а также при интенсивном редактировании этого кода (особенно, когда обычным спутником такого редактирования является метод copy'n'paste), можно легко передать адрес одного массива, а размер другого, просто не уследив глазами за именами в аргументах. Хочется избавиться от этого недостатка. Очевидное решение в стиле С - использовать макроподстановку препроцессора:
CODE
#define PASS_ARRAY(a)  a, sizeof(a)

f(PASS_ARRAY(A));

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

В общем, основная проблема в низкоуровневом представлении массива на С, который при передаче в функцию даунгрейдится до указателя и внутри функции уже видится просто как адрес на объект своего типа. Хочется обойти этот недостаток - ведь в точке вызова-то у компилятора вся информация о массиве, переданном в качестве аргумента, есть.

Вопрос. Имеется ли тут хорошее решение на С или С++. И если имеется, то какое?

P.S. Забегая вперёд, скажу, от ответ знаю, но, во-первых, возможно есть и другие, получше, поинтереснее, во-вторых, сама по себе задачка, имхо, очень интересная и достойная того, чтобы поломать немножко над ней голову, тем более, что практическое решение явно полезно, а не просто академическая головоломка. sm.gif

P.P.S Если кто-то знает решение с ходу, просьба не сообщать его сразу - дайте другим тоже потерзать задачку. Сообщите только, что знаете решение (это, возможно, лучше мотивирует других решателей). sm.gif
Сергей Борщ
QUOTE (dxp @ Sep 10 2012, 10:02) *
P.P.S Если кто-то знает решение с ходу, просьба не сообщать его сразу - дайте другим тоже потерзать задачку. Сообщите только, что знаете решение (это, возможно, лучше мотивирует других решателей). sm.gif
Вроде бы знаю, на плюсах...
gerber
Для работы с массивом на С совсем необязательно знать его размер ... wink.gif
dxp
QUOTE (gerber @ Sep 10 2012, 15:40) *
Для работы с массивом на С совсем необязательно знать его размер ... wink.gif

Вот как! А как, например, обработать все элементы массива? Скажем, подсчитать сумму элементов (если это инты)?

QUOTE (Сергей Борщ @ Sep 10 2012, 15:13) *
Вроде бы знаю, на плюсах...

Гуд, у тебя в асе спрошу. sm.gif

Добавлено:

Сергей Борщ - зачёт! sm.gif
_Pasha
Не пользовался (юзал только препроцессор), но в GCC - элементарно.
dxp
QUOTE (_Pasha @ Sep 10 2012, 18:56) *
Не пользовался (юзал только препроцессор), но в GCC - элементарно.

А как в GCC? Там какой-то свой язык С/С++?
gerber
Цитата(dxp @ Sep 10 2012, 13:05) *
Вот как! А как, например, обработать все элементы массива? Скажем, подсчитать сумму элементов (если это инты)?

Элементарно. Должен быть маркер последнего элемента массива. Как у "нуль-терминированных" строк. Если это массив указателей - нулевой указатель. Тогда перебор всех элементов массива идёт не циклом for, а циклом while.
С массивом произвольных интов сложнее, но на практике совсем произвольных данных не бывает rolleyes.gif , поэтому всегда по смыслу можно подобрать значение, годящееся на роль терминатора массива.
_Артём_
Цитата(gerber @ Sep 10 2012, 15:46) *
Тогда перебор всех элементов массива идёт не циклом for, а циклом while.

А почему for нельзя? У for есть какие-то ограничения не позволяющие его использовать?
ViKo
Цитата(gerber @ Sep 10 2012, 15:46) *
Элементарно. Должен быть маркер последнего элемента массива...

Вы изобретаете нечто, как минимум, не универсальное.
ig_z
QUOTE (dxp @ Sep 10 2012, 10:02) *
Вопрос. Имеется ли тут хорошее решение на С или С++. И если имеется, то какое?

P.P.S Если кто-то знает решение с ходу, просьба не сообщать его сразу - дайте другим тоже потерзать задачку. Сообщите только, что знаете решение (это, возможно, лучше мотивирует других решателей). sm.gif


На с++ есть, и это решение давно присутствует в бусте ессно sm.gif
MrYuran
Цитата(dxp @ Sep 10 2012, 16:26) *
А как в GCC? Там какой-то свой язык С/С++?

B GCC4.7 заявлена поддержка С++0х, в котором заявлена реализация циклов foreach

Но это по слухам.

Что ещё приходит в голову...
Завернуть массив в структуру, состоящую из собственно массива и константного поля длины.
Надеяться, что оптимизатор выкинет второе поле, подставив длину по месту.
Это если просто в си
_Pasha
Цитата(dxp @ Sep 10 2012, 15:26) *
А как в GCC? Там какой-то свой язык С/С++?

Ответ лежит в личке.

Цитата(MrYuran @ Sep 10 2012, 16:14) *
Завернуть массив в структуру, состоящую из собственно массива и константного поля длины.
Надеяться, что оптимизатор выкинет второе поле, подставив длину по месту.
Это если просто в си

Если сравнить с дефайном, то это хуже.
kurtis
1. В нулевом элементе хранить размер массива.
2. Если используются сложные типы данных, то можно использовать связанный список, если не подразумевается извлечение произвольного элемента, или время извлечения элемента не критична.
dxp
QUOTE (gerber @ Sep 10 2012, 19:46) *
Элементарно. Должен быть маркер последнего элемента массива. Как у "нуль-терминированных" строк. Если это массив указателей - нулевой указатель. Тогда перебор всех элементов массива идёт не циклом for, а циклом while.
С массивом произвольных интов сложнее, но на практике совсем произвольных данных не бывает rolleyes.gif , поэтому всегда по смыслу можно подобрать значение, годящееся на роль терминатора массива.

Это вы придумываете условия задачи. Исходно массивы есть как есть, и тип элемента в них может быть произвольным. Никаких предположений о возможности изменения содержимого первого или терминальнго элементов делать нельзя. Да и не нужно тут это - применять рантаймные проверки там, где вся информация о размере есть у комилятора на этапе компиляции и доступна в точке вызова. Задача лишь в том, чтобы автоматом доставить эту информацию внутрь функции.

QUOTE (ig_z @ Sep 10 2012, 19:59) *
На с++ есть, и это решение давно присутствует в бусте ессно sm.gif

Вам зачёт! sm.gif


Вижу, что решение многим известно, но пока не будем открывать, пусть для желающих останется некий challenge. В принципе, уже ясно, что в сети решение найти не проблема, но у нас тут спорт и от всех ожидается спортивное поведение. sm.gif
Xenia
Всё это - палиативные решения. Окажется массив двухмерным - опять возникнут проблемы пуще прежних.

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

А в пределе можно встроить в этот класс частно используемые фукции, типа обнуления, инициализации константой, умножения на число, нахождение суммы, минимального и максимального элемента и т.д. А при должном уровне упорства sm.gif определить операции над такими массивами на матричный манер - реализовать их умножение, разложение на множители, FFT и прочие преобразования, и очень много еще чего другого на собственный вкус.

Если не лезть на рожон (не определять функции вируальными), то компилятор будет компилировать только те функции класса, которые находят спрос в конкретной программе. Поэтому "излишества" не помешают, зато создадут удобную среду для работы.
kurtis
Так какой правильный ответ?
dxp
QUOTE (_Pasha @ Sep 10 2012, 20:41) *
Ответ лежит в личке.

Посмотрел, спасибо. Насколько понял, это несколько не то - эта штука чекает размер с целью контроля переполнения памяти. Но она никак не поможет узнать размер массива внутри функции, которая видит указатель. Ну, и весьма частное решение, даже если бы делало то, что надо.

QUOTE (Xenia @ Sep 10 2012, 22:29) *
Всё это - палиативные решения. Окажется массив двухмерным - опять возникнут проблемы пуще прежних.

Двумерных массивов в С/С++ не бывает.

QUOTE (Xenia @ Sep 10 2012, 22:29) *
Если решать проблему, то на корню. Создавать класс, в которую входит указатель на массив

Не нужно придумывать условия задачи. Они уже определены и вполне однозначны и просты. И решение есть, простое и эффективное.


QUOTE (kurtis @ Sep 10 2012, 22:36) *
Так какой правильный ответ?

Давайте ещё немножко подождём, до завтра, до середины дня, пусть хотя бы сутки будут на решение.
amaora
С макросами я бы делал как-то вот так.

Код
#define AR(a)    (struct ar __a = {(void *) (a), sizeof(a)}, __a)

struct ar {
void *p;
int sz;
};

#define FOO(a)   foo(AR(a))

void foo(struct ar a);

...

int A[] = {1, 2, 3};

FOO(A);


Точнее я бы так не делал sm.gif Обычно можно проще, но для этого надо видеть всю задачу, в такой постановке ничего не поделать.
Rst7
QUOTE
Двумерных массивов в С/С++ не бывает.


Шутить изволите?

CODE
int A[5][6];

int main()
{
  A[2][3]=123;
  A[4][2]=456;
  A[2][4]=789;
...


CODE

24 int A[5][6];
\ A:
\ 00000000 DS8 120
...
\ 00000004 .... LDR.N R0,??DataTable0
\ 00000006 7B21 MOVS R1,#+123
\ 00000008 C163 STR R1,[R0, #+60]
29 A[4][2]=456;
\ 0000000A 4FF4E471 MOV R1,#+456
\ 0000000E 8166 STR R1,[R0, #+104]
30 A[2][4]=789;
\ 00000010 40F21531 MOVW R1,#+789
\ 00000014 0164 STR R1,[R0, #+64]
...
\ ??DataTable0:
\ 00000000 ........ DC32 A


Хотя, я конечно понимаю, что суть двумерного массива всего лишь массив массивов wink.gif
ViKo
Я бы пользовался макроподстановкой, описанной в первом сообщении
Но, если не нравится так, можно заменить саму функцию
Код
#define a_s(array) arr_sum(array, sizeof(array)/sizeof(array[0]))

uint32_t arr_sum(uint32_t *p, uint32_t s);
...
Xenia
Еще бы темплейты задействовать sm.gif
_Pasha
Цитата(dxp @ Sep 10 2012, 18:54) *
Насколько понял, это несколько не то - эта штука чекает размер с целью контроля переполнения памяти.

По идее, она возвращает размер, тип возврата - size_t. Вроде всё ОК. А дальше по Жванецкому: включаем - не работает. sad.gif Странно.
dxp
QUOTE (Rst7 @ Sep 11 2012, 00:02) *
Шутить изволите?

Ничуть.

QUOTE (Rst7 @ Sep 11 2012, 00:02) *
CODE
int A[5][6];

int main()
{
  A[2][3]=123;
  A[4][2]=456;
  A[2][4]=789;
...


[...]
Хотя, я конечно понимаю, что суть двумерного массива всего лишь массив массивов wink.gif

Именно. А массив массивов - это ни разу не двумерный массив. sizeof элемента массива всегда один и тот же по определению (т.к. массив - агрегатный объект, состоящий из элементов одного типа). Для настоящего (тру Ъ) двумерного массива нотация A[0] не имеет смысла, т.к. не индексирует элемент, а в С/С++ вполне имеет, но возвращает указатель на массив (точнее, указатель на указатель). Помнится, на фидоэхе su.с-сpp один очень квалифицированный человек весьма наглядно и на примерах показал ключевую разницу между этими вещами и к чему приводит неверная трактовка. Впрочем, соглашусь с тем, что сам по себе термин "двумерный массив" в C/C++ вполне имеет право быть, только нужно всегда иметь в виду контекст применения этого термина.

QUOTE (Xenia @ Sep 11 2012, 01:31) *
Еще бы темплейты задействовать sm.gif

Это мысль в правильном направлении.

QUOTE (_Pasha @ Sep 11 2012, 01:35) *
По идее, она возвращает размер, тип возврата - size_t. Вроде всё ОК. А дальше по Жванецкому: включаем - не работает. sad.gif Странно.

Она-то возвращает, но ей надо видеть определение объекта в точке вызова. Если это сделать при вызове функции - при передаче аргументов, то оно сработает, но ровно так же работает и sizeof(), с этим нет проблем. А вот внутри функции аргумент вырождается в указатель - что оно тут сможет извлечь?
Rst7
QUOTE
Для настоящего (тру Ъ) двумерного массива нотация A[0] не имеет смысла, т.к. не индексирует элемент, а в С/С++ вполне имеет, но возвращает указатель на массив (точнее, указатель на указатель).


Ну, во-первых, не указатель на указатель, а указатель на элемент. Непонятно только, чему это противоречит.

Хотите получить sizeof элемента - так и берите элемент - a[i][j].

sizeof(а[0]) тоже имеет вполне логичное значение, равное размеру строки в массиве.

Все четко и логично.
dxp
QUOTE (Rst7 @ Sep 11 2012, 10:35) *
Ну, во-первых, не указатель на указатель, а указатель на элемент. Непонятно только, чему это противоречит.

Хотите получить sizeof элемента - так и берите элемент - a[i][j].

sizeof(а[0]) тоже имеет вполне логичное значение, равное размеру строки в массиве.

Все четко и логично.

Будь это настоящим двумерным массивом, операция a + 1 давала бы адрес следующего элемента, а в нашем случае это будет адрес следующего массива. Разница существенная. Повторять всё обилие различий не хочется, тут подробнее.
Rst7
QUOTE
Повторять всё обилие различий не хочется, тут подробнее.


Бред там написан.
QUOTE
Будь А полноценным двумерным массивом - т.е. законченным, целостным
объектом, то выражение:
А + 1
означало бы адрес следующего такого двумерного массива,


Например, int A[10]; и последующее выражение A+1 указывает не на A[10], а на A[1], так почему там требуют другого поведения, соответствующему, кстати, указателю на двумерный массив?
dxp
QUOTE (Rst7 @ Sep 11 2012, 12:04) *
Например, int A[10]; и последующее выражение A+1 указывает не на A[10], а на A[1], так почему там требуют другого поведения, соответствующему, кстати, указателю на двумерный массив?

Допустим, у нас есть настоящий честный двумерный массив А[10][20]. Что возвращает A + 1? И что возвращает это выражение в случае сишного массива?
Rst7
QUOTE
Допустим, у нас есть настоящий честный двумерный массив А[10][20]. Что возвращает A + 1?


Приведите пример языка с "настоящим честным двумерным массивом", в котором допустима операция A+1.

Еще раз, Ваше утверждение (и утверждение по ссылке, которую Вы дали) требует поведения при операции A+1 такого, какое в Си записывается следующим образом:

CODE
int (*a)[5][6];

(*a)[1][2]=123; //Запись значения в первый двумерный массив
(*(a+1))[1][2]=345; //Запись значения во второй массив


Несмотря на то, что в 99% случаев утверждают, что в Си массивы и указатели есть одно и тоже, на самом деле между ними есть четкая разница. Непонимание порождает путаницу.
dxp
QUOTE (Rst7 @ Sep 11 2012, 14:47) *
Приведите пример языка с "настоящим честным двумерным массивом", в котором допустима операция A+1.

Например, язык пакета Matlab. Или какой-нибудь другой высокоуровневый язык (тот же питон с библиотекой numpy). В низкоуровенвых языках, как С/C++, паскаль и подобных, представление соответствует уровню, т.е. является тоже низкоуровневым - непрерывная область памяти с элементами одного и того же типа. Максимум что они позволяют - массивы массивов.

Тем не менее концепция двумерного массива никуда не девается. В мире полно объектов, которые описываются именно двумерным массивом, а не массивом массивов. Попытка адресоваться по одной координате всегда приводит к декомпозиции исходного типа и он перестаёт существовать. Например, есть объект - точка на экране. Она характеризуется двумя координатами. И изображение на экране - это двумерный массив этих точек. Попытка обращаться только через одну координату автоматически переводит адресуемый элемент из точки в строку или столбец.

Нормальное представление двумерного массива вполне допускает обращение к его фрагментам - строкам, столбцам и просто прямоугольным фрагментам, и предоставляет для этого соответствующие средства (slice). Вы показывали, как ловко на С получить строку из "двумерного" массива. Покажите, как получить столбец? Или прямоугольный фрагмент?


QUOTE (Rst7 @ Sep 11 2012, 14:47) *
Еще раз, Ваше утверждение (и утверждение по ссылке, которую Вы дали) требует поведения при операции A+1 такого, какое в Си записывается следующим образом:

CODE
int (*a)[5][6];

(*a)[1][2]=123; //Запись значения в первый двумерный массив
(*(a+1))[1][2]=345; //Запись значения во второй массив

Настоящий двумерный массив в принципе не поддерживает такую нотацию обращения - через разыменовывание.

QUOTE (Rst7 @ Sep 11 2012, 14:47) *
Несмотря на то, что в 99% случаев утверждают, что в Си массивы и указатели есть одно и тоже, на самом деле между ними есть четкая разница. Непонимание порождает путаницу.

Самая эта тема сутью задачи подчёркивает эту разницу. sm.gif

Собственно, возвращаемся к точке начала спора. Спор о терминах. Понимание сути у всех есть. Двумерных массивов в С/C++ нет. Есть массивы массивов. Разница между ними есть. В ряде случаев массив массивов может использоваться в качестве двумерного массива с некоторыми ограничениями. Учитывая этот контекст, вполне допустимо употреблять термин "двумерный массив" для краткости (и лучше оговаривать сразу, что имеется в виду), но всегда помнить, что за этим стоит. На этом предлагают дискуссию закончить, дабы не тратить время и ходить по кругу.
dxp
Наверное, пришло время обнародовать решение, как вчера было обещано.

Решение на самом деле очень простое и эффективное. Выглядит так:
CODE
template <typename T, size_t N>
void f(T (&a)[N])
{
    ... // используем N - это длина массива
}


В общем случае это не тот размер, который возвращает sizeof(), а именно длина массива, но именно она-то и нужна. А размер получается тоже элементарно - N*sizeof(T). Всё на этапе компиляции.

Возможно, не всем понятно, как это работает, поэтому краткое пояснение. В С++ инстанцирование функции по шаблону может быть явным, когда задаются параметры шаблона, и неявным, когда компилятор сам исходя из контекста использования, может сгенерировать реализацию. Именно вот такое неявное инстанцирование тут и используется. При этом вся информация у компилятора есть - есть объект-массив, про который всё известно - и тип элементов, и длина массива. Далее, ключевой момент: аргументом функции заявлена ссылка на массив, состоящий из элементов типа T и длиной N. Теперь в точке вызова:

CODE
int A[] = { 1, 2, 3, 4 };
...

f(A);

компилятор находит использование функции f(), ищет её реализацию, находит шаблон, анализирует контекст (а по контексту требуется именно массив - ссылка на него, а не указатель) и генерирует функцию f(int &а[4]). Всё, дело сделано. Поскольку параметры шаблона доступны внутри определения функции, то доступен и тип, и длина массива. Все действия производятся на этапе компиляции, т.е. получение длины массива достигается без единой инструкции, выполняемой на рантайме. Пример:

CODE
template <typename T, int N>
int len(T (& a)[N])
{
    return N;
}

const int a[] = {1,1,1,1,2,3,4,5,6}; // 9 элементов
const int b[] = {1,1,1,5,6};  // 5 элементов

volatile int d;

void main()
{
    d = len(a);
    d = len(b);

}


Результат (MSP430/IAR):

CODE
              d = len(a);              
000000   B2400900.... MOV.W   #0x9, &d  
              d = len(b);              
000006   B2400500.... MOV.W   #0x5, &d
kurtis
А чем это принципиально отличается от sizeof(a)/sizeof(a[0])?
dxp
QUOTE (kurtis @ Sep 11 2012, 18:46) *
А чем это принципиально отличается от sizeof(a)/sizeof(a[0])?

А разве из первого поста не ясно, из чего вся задача возникла? Передайте в функцию массив, чтобы внутри неё был известен размер. Размер вторым аргументом не передавать. Перечитайте первый пост.
kurtis
Опять не понял

Код
#include <iostream>

template <typename T, int N>
int len(T (& a)[N])
{
    return N;
}

const int a[] = {1,1,1,1,2,3,4,5,6}; // 9 элементов
const int b[] = {1,1,1,5,6};  // 5 элементов

volatile int d;

int foo(const int arr[]) {
    std::cout << "len:" <<  len(arr) << "\n";
}

int main()
{
    foo(a);
    foo(b);
}

Цитата
mike@mike-work:/tmp$ LC_ALL=C g++ test.cpp
test.cpp: In function 'int foo(const int*)':
test.cpp:15:33: error: no matching function for call to 'len(const int*&)'
test.cpp:15:33: note: candidate is:
test.cpp:4:5: note: template<class T, int N> int len(T (&)[N])

MrYuran
Закорючка лишняя.
Либо если уж разыменовывать, то &arr[0]
amaora
А компиляторы C++ уже способны это оптимизировать? GCC похоже может либо сделать inline либо много реализаций одного и тоже же. А так чтобы функция одна а параметры разные? только через обертку?
dxp
QUOTE (kurtis @ Sep 11 2012, 18:57) *
Опять не понял

Что тут не понятного? Вы слепили свою функцию, скормили ей массив, внутри функции массив уже не виден, а виден только указатель на него - и что, вы хотите, чтобы компилятор восстановил из указателя массив?

Заведите массив внутри вашей функции и передавайте его функции len(). Массив, а не указатель.

Именно тут и проявилась фича метода - невозможно по ошибке передать в функцию указатель - только массив. Такая ошибка гарантировано отлавливается на этапе компиляции.
ViKo
Цитата(dxp @ Sep 11 2012, 18:22) *
Именно тут и проявилась фича метода - невозможно по ошибке передать в функцию указатель - только массив.

Такая же особенность будет в простеньком
#define a_s(array) arr_sum(array, sizeof(array)/sizeof(array[0]))
kurtis
Цитата
Заведите массив внутри вашей функции и передавайте его функции len(). Массив, а не указатель.

Можете, пожалуйста, привести код, который решает проблему из первого сообщения?
AHTOXA
Цитата(kurtis @ Sep 11 2012, 23:20) *
Можете, пожалуйста, привести код, который решает проблему из первого сообщения?

Код
#include <iostream>

template <typename T, int N>
int len(T (& a)[N])
{
    return N;
}

const int a[] = {1,1,1,1,2,3,4,5,6}; // 9 элементов
const int b[] = {1,1,1,5,6};  // 5 элементов

int main()
{
    std::cout << "len a:" <<  len(a) << "\n";
    std::cout << "len b:" <<  len(b) << "\n";
}
kurtis
Чем это принципиально отличается от sizeof и как мне внутри функции узнать размер переданого массива? (ведь в этом смысл вопроса был?)
_Pasha
Цитата(ViKo @ Sep 11 2012, 20:03) *
Такая же особенность будет в простеньком
#define a_s(array) arr_sum(array, sizeof(array)/sizeof(array[0]))

Это для числа элементов. А для просто размера - пойдем в обход, чтобы выловить передачу не-массива
Код
#define a_s(array) arr_sum(array, (sizeof(array)/sizeof(array[0])*sizeof(array[0]))

sm.gif
PS Задумался... в "чиста Си" от GCC уже столько костылей плюсовых... ужас - бери и делай закат Солнца.
dxp
QUOTE (ViKo @ Sep 12 2012, 00:03) *
Такая же особенность будет в простеньком
#define a_s(array) arr_sum(array, sizeof(array)/sizeof(array[0]))

В том-то и дело, что нет. sm.gif Когда вы скормите указатель в тут шаблонную функцию, вы получите ругань компилятора. А в случае макроса код будет молча скомпилирован, но размер будет получен неверный - не длина массива, а sizeof() указателя. В этом и состоит подлость, что закрадывается ошибка, которую компилятор отловить не в силах. Шаблонная функция лишена этого недостатка.

QUOTE (kurtis @ Sep 12 2012, 02:04) *
Чем это принципиально отличается от sizeof и как мне внутри функции узнать размер переданого массива? (ведь в этом смысл вопроса был?)

Вы разницу между f(A) и f(A, sizeof(A)/sizeof(A[0]) видите? Перечитайте, пожалуйста, самый первый пост внимательно, там подробно описано, чем не устраивает второй вариант и чего хочется добиться. Для конкретности, представьте, что задача состоит не в определении длины массива, а в подсчёте суммы его элементов, функция должна вернуть эту сумму.
kurtis
Медлено начинаю понимать...

Код
#include <iostream>

template <typename T, int N>
int sum(T (& a)[N])
{
    int sum, i;
    for (i = 0, sum = 0; i < N; i++)
        sum += a[i];

    return sum;
}

const int a[] = {1,1,1,1,2,3,4,5,6}; // 9 элементов
const int b[] = {1,1,1,5,6};  // 5 элементов

int main()
{
    std::cout << sum(a) << "\n";
    std::cout << sum(b) << "\n";
}

Цитата
mike@mike-P55A-UD4:/tmp$ for i in $(nm a.out | grep sum | cut -d" " -f 3) ; do echo $i ; c++filt $i ; done
_Z3sumIKiLi5EEiRAT0__T_
int sum<int const, 5>(int const (&) [5])
_Z3sumIKiLi9EEiRAT0__T_
int sum<int const, 9>(int const (&) [9])

При выключенной оптимизации, получается 2 экземпляра функции sum(), для каждого из параметров. Конечно, это все хорошо если функции простые, и компилятор может их оптимизировать, а если нет?
AHTOXA
Цитата(kurtis @ Sep 12 2012, 01:04) *
Чем это принципиально отличается от sizeof и как мне внутри функции узнать размер переданого массива? (ведь в этом смысл вопроса был?)

Вы не совсем поняли идею. Идея именно в том, что это аналог sizeof(), но за программиста этот sizeof() автоматически подставляет компилятор.
То есть, вы вместо функции
Код
void fome_func(int arr[], size_t arr_len)
{
  for (int i = 0; i < arr_len; i++)
    foo(arr[i]);
}
пишете шаблон:
Код
template <typename T, int arr_len>
void fome_func(T (& arr)[arr_len])
{
  for (int i = 0; i < arr_len; i++)
    foo(arr[i]);
}

Здесь исключена возможность передать размер, не соответствующий массиву - это делает компилятор.
Однако при таком подходе возможно, что у вас для каждого размера массива будет сгенерирована своя функция.
Если вы этого не желаете, то можно совместить подходы:
Код
template <typename T, int arr_len>
inline void call_fome_func(T (& arr)[arr_len])
{
  fome_func(arr, arr_len);
}

Это практически полный аналог варианта с #define, но более безопасный с точки зрения контроля параметров.
kurtis
Спасибо. Ситуация начинает проясняться.

Да, в таком контексте, действительно, интересный способ.
ViKo
Цитата(dxp @ Sep 12 2012, 04:36) *
В том-то и дело, что нет. sm.gif Когда вы скормите указатель в ту шаблонную функцию, вы получите ругань компилятора. А в случае макроса код будет молча скомпилирован, но размер будет получен неверный - не длина массива, а sizeof() указателя. В этом и состоит подлость, что закрадывается ошибка, которую компилятор отловить не в силах. Шаблонная функция лишена этого недостатка.

Вы правы. Передал в функцию a_s() указатель, получилась длина 1. И отсутствие элемента array[0] компилятор не беспокоит. Keil не выдает даже предупреждения.
Видимо, это должно подтверждать, что разница между массивами и указателями меньше, чем хотелось бы. sm.gif
P.S. можно даже sizeof(array[1]) использовать, и все равно компилятор не ругается. Можно даже sizeof(array[1000000]).
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.