Длина массива в аргументе функции
это хорошо известный код для вычисления длины массива в C:
sizeof(array)/sizeof(type)
но я не могу найти длину массива, переданного в качестве аргумента функции:
#include <stdio.h>
int length(const char* array[]) {
return sizeof(array)/sizeof(char*);
}
int main() {
const char* friends[] = { "John", "Jack", "Jim" };
printf("%d %d", sizeof(friends)/sizeof(char*), length(friends)); // 3 1
}
Я предполагаю, что массив копируется значение аргумента функции, как постоянный указатель и ссылка на него должна решить эту проблему, но эта декларация не действует:
int length(const char**& array);
Я нахожу, что передача длины массива в качестве второго аргумента является избыточной информацией, но почему стандарт декларация main
такой:
int main(int argc, char** argv);
пожалуйста, объясните, можно ли узнать длину массива в аргументе функции, и если да, то почему существует избыточность в main
.
8 ответов:
sizeof
только работает, чтобы найти длину массива, если вы применяете его к исходному массиву.int a[5]; //real array. NOT a pointer sizeof(a); // :)
однако к тому времени, когда массив распадается на указатель, sizeof даст размер указателя, а не массива.
int a[5]; int * p = a; sizeof(p); // :(
как вы уже умно указали main получает длину массива в качестве аргумента (argc). Да, это по необходимости и не является излишним. (Ну, это своего рода reduntant, так как argv удобно завершается нулевым указателем, но я отвлекаюсь)
есть некоторые рассуждения о том, почему это произойдет. Как мы можем сделать так, чтобы массив C также знал свою длину?
первой идеей было бы не иметь массивов, распадающихся на указатели, когда они передаются функции и продолжают сохранять длину массива в системе типов. Плохо то, что вам нужно будет иметь отдельную функцию для каждой возможной длины массива и делать это не очень хорошая идея. (Паскаль сделал это, и некоторые люди думают, что это одна из причин, по которой он "потерял" C)
вторая идея заключается в сохранении длины массива рядом с массивом, как и любой современный язык программирования:
a -> [5];[0,0,0,0,0]
но тогда вы просто создаете невидимое
struct
за кулисами и философия C не одобряет такого рода накладные расходы. Тем не менее, создание такой структуры самостоятельно часто является хорошей идеей для некоторых видов проблемы:struct { size_t length; int * elements; }
еще одна вещь, о которой вы можете думать, - это то, как строки в C заканчиваются null вместо хранения длины (как в Pascal). Чтобы сохранить длину, не беспокоясь о пределах нужно целых четыре байта, невообразимо дорогая сумма (по крайней мере, тогда). Можно было бы задаться вопросом, могут ли массивы также завершаться null, но как тогда вы позволите массиву хранить null?
массив распады к указателю при передаче.
раздел 6.4 C FAQ покрывает это очень хорошо и обеспечивает ссылки К&Р ЕТК.
это в стороне, представьте, что функция может знать размер памяти, выделенной в указателе. Вы можете вызвать функцию два или более раз, каждый раз с разными входными массивами, которые потенциально имеют разную длину; поэтому длина должна быть передана как секретная скрытая переменная каким-то образом. А затем рассмотрим, если вы передали смещение в другой массив, или массив, выделенный в куче (
malloc
и все это библиотечные функции-то, на что компилятор ссылается, а не видит и рассуждает о теле).его становится трудно представить, как это может работать без некоторых закулисных объектов среза и такого права?
Symbian действительно был
AllocSize()
функция, которая возвращает размер выделения сmalloc()
; это работало только для литерального указателя, возвращенного malloc, и вы получите gobbledygook или сбой, если вы попросите его узнать размер недопустимого указателя или смещение указателя от одного.вы не хотите верить, что это невозможно, но это действительно не так. единственный способ узнать длину чего-то, переданного в функцию, - это отслеживать длину самостоятельно и передавать ее в себя как отдельный явный параметр.
как указано в @Will, распад происходит во время передачи параметров. Один из способов обойти это-передать количество элементов. Чтобы добавить к этому, вы можете найти
_countof()
макрос полезный - он делает эквивалент того, что вы сделали;)
во-первых, лучшее использование для вычисления количества элементов, когда фактическое объявление массива находится в области действия:
sizeof array / sizeof array[0]
таким образом, вы не повторяете имя типа, которое, конечно же, может измениться в объявлении и привести к неправильному вычислению длины. Это типичный случай не повторяйся.
во-вторых, как незначительный момент, обратите внимание, что
sizeof
не является функцией, поэтому выражение выше не нуждается в скобках вокруг аргумента кsizeof
.в-третьих, C не имеет ссылок, поэтому ваше использование
&
в декларации не будет работать.Я согласен, что правильное решение C состоит в том, чтобы передать длину (используя
size_t
type) в качестве отдельного аргумента, и использоватьsizeof
в том месте, где выполняется вызов, если аргумент является" реальным " массивом.обратите внимание, что часто вы работаете с памятью вернулись, например,
malloc()
, и в этих случаях у вас никогда не будет "истинного" массива для вычисления размер выключен, поэтому проектирование функции для использования количества элементов является более гибким.
относительно int main ():
согласно стандарту,
argv
указывает на нуль -расторгнут массив (указателей на null-завершенной строки). (5.1.2.2.1:1).то есть
argv = (char **){ argv[0], ..., argv[argc - 1], 0 };
.следовательно, вычисление размера выполняется функцией, которая является тривиальной модификацией
strlen()
.
argc
- это только там, чтобы сделатьargv
расчет длины O (1).метод count-until-NULL будет не работа для ввода универсального массива. Вам нужно будет вручную указать размер в качестве второго аргумента.
Это старый вопрос, и OP, похоже, смешивает C++ и C в своих намерениях/примерах. В C, когда вы передаете массив функции, он распадается на указатель. Таким образом, нет никакого способа передать размер массива, кроме как с помощью второго аргумента в функции, которая хранит размер массива:
void func(int A[]) // should be instead: void func(int * A, const size_t elemCountInA)
Это очень мало случаев, когда вам это не нужно, например, когда вы используете многомерные массивы:
void func(int A[3][whatever here]) // That's almost as if read "int* A[3]"
использование нотации массива в сигнатуре функции по-прежнему полезно, для разработчика, так как это может быть помощь, чтобы сказать, сколько элементов ваши функции ожидает. Например:
void vec_add(float out[3], float in0[3], float in1[3])
легче понять, чем этот (хотя ничто не мешает доступу к 4-му элементу в функции в обеих функциях):
void vec_add(float * out, float * in0, float * in1)
если вы используете C++, то вы можете фактически захватить размер массива и получить то, что вы ожидаете:
template <size_t N> void vec_add(float (&out)[N], float (&in0)[N], float (&in1)[N]) { for (size_t i = 0; i < N; i++) out[i] = in0[i] + in1[i]; }
в этом случае компилятор будет гарантировать, что вы не добавляете 4D вектор с 2D вектор (что невозможно в C без передачи размерности каждого измерения в качестве аргументов функции). Будет столько же экземпляров функции vec_add, сколько и количество измерений, используемых для ваших векторов.