Передача массива в функцию без указания размерности

ssvs111
ssvs111 аватар
Offline
Зарегистрирован: 11.07.2014

Задача состоит в следующем: Нужно передать одномерный массив в функцию таким образом, чтобы функция сама определяла размерность массива. Предполагается, что размерность передаваемых массивов может быть разной и на момент написания программы не будет известна, или известна, но чтобы не париться указывая второй аргумент функции.

 

Уважаемые господа, помогите решить этот вопрос. Заранее знаю что это возможно.

boban_
Offline
Зарегистрирован: 09.10.2013

В языке С нельзя передать весь массив как аргумент функции. Однако можно передать указатель на массив, т.е. имя массива без индекса. Например, в представленной программе в func1() передается указатель на массив i:
int main(void)
{
int i[10];

func1(i);

/* ... */
}

Если в функцию передается указатель на одномерный массив, то в самой функции его можно объявить одним из трех вариантов: как указатель, как массив определенного размера и как массив без определенного размера. Например, чтобы функция func1() получила доступ к значениям, хранящимся в массиве i, она может быть объявлена как
void func1(int *x) /* указатель */
{
/* ... */
}

или как
void func1(int x[10]) /* массив определенного размера */
{
/* ... */
}

и наконец как
void func1(int x[]) /* массив без определенного размера */
{
/* ... */
}

Эти три объявления тождественны, потому что каждое из них сообщает компилятору одно и то же: в функцию будет передан указатель на переменную целого типа. В первом объявлении используется указатель, во втором — стандартное объявление массива. В последнем примере измененная форма объявления массива сообщает компилятору, что в функцию будет передан массив неопределенной длины. Как видно, длина массива не имеет для функции никакого значения, потому что в С проверка границ массива не выполняется. Эту функцию можно объявить даже так:
void func1(int x[32])
{
/* ... */
}

И при этом программа будет выполнена правильно, потому что компилятор не создает массив из 32 элементов, а только подготавливает функцию к приему указателя.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Один из простых вариантов:

int array[18];
#define array_count sizeof(array)/sizeof(array[0])

и вторым параметром передавать в функцию array_count. Если длина массива изменится в процессе разработки, то всё равно программа останется рабочей. Возможно есть и другие варианты, только у меня за много лет не было нужды делать такое. Да и не вижу проблем передавать длину массива.

PS array_count можно объявить и как константу. const int array_count = sizeof...

UPD void foo(int *pp_array, int p_array_cnt) {}

 

Radjah
Offline
Зарегистрирован: 06.08.2014

Передать указатель на начало массива и топать до элемента-терминатора как в случае со строками.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

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

Radjah
Offline
Зарегистрирован: 06.08.2014

> содержит информацию о длине в своих данных (терминальный символ или там длина сидит в первом элементе

Про делфи/паскальные строки совсем забыл. Тоже вариант сообщения длины.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Radjah пишет:

> содержит информацию о длине в своих данных (терминальный символ или там длина сидит в первом элементе

Про делфи/паскальные строки совсем забыл. Тоже вариант сообщения длины.

Вы про первый элемент? Ну, да. Только, похоже, ТС'у это не нужно, он нас потроллил просто.

ssvs111
ssvs111 аватар
Offline
Зарегистрирован: 11.07.2014

 Все же решение есть, и оно работает без терминаторов и т.д.

Все началось с  библиотеки EEPROM2,

https://github.com/ssvs111/ARDUINO_EEPROM2

 которая по команде   EEPROM_write(32, my_array);  определяла длину my_array и записывала его в EEPROM-е начиная с ячейки #32.

Также переменная my_array могла иметь абсолютно любой тип – byte, int, long, array и т.д.

Попытки изучить .cpp и .h  файлы библиотеки EEPRM2 мало к  чему привели, т.к. использовалось неизвестное мне выражение template< typename T > которое во всем этом играет немалую роль.

Попытки  вытащить нужный код из .cpp и .h  также не чему не привели так как функция взятия размера sizeof() входной переменной почему-то не работает в скетче , а работает в библиотеке.

Поэтому я недолго думая скопировал нужный код из  .cpp и .h библиотеки EEPROM2 в созданную библиотеку  CCNET

https://github.com/ssvs111/CCNET

в библиотеке только одна функция, которая отправляет в SERIAL входной массив данных,

а вот пример, в котором показано  как отправляется массив в функцию без указания размера

#include <CCNET.h> //библиотека с функцией, самостоятельно определяющей 
//   размерность входного массива
byte RESET[] = {0x02, 0x03, 0x09, 0x37, 0x00, 0x00, 0x0, 0x81, 0x1F};//9 байт
byte POLL[] = {0x02, 0x03, 0x06, 0x30, 0x47, 0xC2};                  //6 байт
void setup () {
  Serial.begin(9600); //связь с компом (мониторим программу)
 Serial.begin(19200); //связь с внешним устройством
  
  //Библиотека CCNET запукает команду sazeof() определяет длину массива
  //И в цикле отправляет последовательно массив на Serial1 внешнему устройству
  CCNET_SEND (RESET); //Отправляем указатель на массив RESET библиотеке CCNET
  delay (100);
  CCNET_SEND (POLL); //Отправляем указатель на массив POLL библиотеке CCNET
}

void loop() {
}

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

Кто-нибудь может это объяснить?

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Это называется шаблон. В 11 и 13 строках выполняются разные функции, потому и работает sizeof.
В гугле море информации, например, здесь http://cppstudio.com/post/5165/

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ssvs111 пишет:

 Все же решение есть, и оно работает без терминаторов и т.д.

Боюсь Вас разочаровать, но таки нет.

ssvs111 пишет:

скопировал нужный код из  .cpp и .h библиотеки EEPROM2 в созданную библиотеку  CCNET

https://github.com/ssvs111/CCNET

в библиотеке только одна функция, которая отправляет в SERIAL входной массив данных,

Не совсем так. Там два элемента. Там сидит шаблон CCNET_SEND, который на этапе компиляции разворачивается в взыов функции sendcc, которой благополучно передаётся длина данных вторым параметром

sendcc(&data, sizeof(data));

ssvs111 пишет:

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

Кто-нибудь может это объяснить?

А чего тут объяснять. Шаблон,

template< typename T > void CCNET_SEND(const T &data) {
   sendcc(&data, sizeof(data));
}

Будучи вызванным строкой

CCNET_SEND (RESET);

На этапе компиляции разворачивается в

 sendcc(&RESET, sizeof(RESET));

а дальше выполняется уже эта функция, которой, как мы видим вторым параметром благополучно передаётся длина.

-----------------------------

На самом деле, пользоваться таким кодом очень опасно, особенно если Вы его толком не понимаете. Он хорош, когда массив-аргумент объявлен явно. Однако, если Вы попробуете воспользоваться этим кодом с честным указателем – Вас ждёт разочарование.

Вот смотрите:

// так работает
byte RESET[] = {0x02, 0x03, 0x09, 0x37, 0x00, 0x00, 0x0, 0x81, 0x1F};//9 байт
CCNET_SEND (RESET);

// а вот так – уже нет 
void SendIt(byte * r) {
   CCNET_SEND (r);
}
byte RESET[] = {0x02, 0x03, 0x09, 0x37, 0x00, 0x00, 0x0, 0x81, 0x1F};//9 байт
SendIt(RESET);

 

DarkDaemon
Offline
Зарегистрирован: 31.10.2015

ЕвгенийП пишет:

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

Благодарю за ответ, долго думал, почему этот код работает не так, как хотелось бы.

void setup() {
  Serial.begin(9600);
  byte a[4] = {1, 2, 3, 4};
  Serial.println(sizeof(a)); //Выводит 4
  test(a);
}

void loop() {
  
}

void test(byte* param){
  byte a = sizeof(param); //Выводит 2
  Serial.println(a);
}

 

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

Это шутка такая?
В одном месте вывод количества байтов, отведенных для переменной 'a', в другом месте вывод количество байтов, которое отведено на указатель для определенной архитектуры процессора.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

DarkDaemon пишет:

Благодарю за ответ, долго думал, почему этот код работает не так, как хотелось бы.

void setup() {
  Serial.begin(9600);
  byte a[4] = {1, 2, 3, 4};
  Serial.println(sizeof(a)); //Выводит 4
  test(a);
}

void loop() {
  
}

void test(byte* param){
  byte a = sizeof(param); //Выводит 2
  Serial.println(a);
}

 

Этот код работает так, как написан.

А что Вам "хотелось бы", никому не известно, включая компилятор.

vladimirneo
Offline
Зарегистрирован: 09.12.2020

Интересно, каким образом функция println() понимает сколько символов (байт) доступно по указателю? Или после массива есть признак окончания типа 0x04 и его можно как-то извлечь?

char fileName = "201209.TXT";
void printArr(char *fileName) {
  println(fileName);  // выводит "201209.TXT"
}
 
DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

vladimirneo пишет:

Интересно, каким образом функция println() понимает сколько символов (байт) доступно по указателю? 

Чтоб не выставлять себя идиотом, почитай. https://server.179.ru/tasks/cpp/total/051.html