81

I am confused about which syntax to use if I want to pass an array of known or unknown size as a function parameter.

Suppose I have these variants for the purpose:

void func1(char* str) {
    //print str
}

void func2(char str[]) {
    //print str
}

void func3(char str[10]) {
    //print str
}

What are the pros and cons of using each one of these?

4
  • 11
    C or C++? Pick one. Commented Apr 22, 2013 at 10:45
  • 7
    What difference does it make whether it's c or c++? Commented Apr 22, 2013 at 17:22
  • 6
    In C you can do a 4th case void func (size_t size, char str[size]), which might be preferable in some situations. Not possible in C++ though. Commented Jun 8, 2015 at 14:16
  • 2
    I agree that it shouldn't have both tags because C and C++ are two independent languages. Commented Apr 22, 2016 at 1:02

8 Answers 8

101

All these variants are the same. C just lets you use alternative spellings but even the last variant explicitly annotated with an array size decays to a normal pointer.

That is, even with the last implementation you could call the function with an array of any size:

void func3(char str[10]) { }

func("test"); // Works.
func("let's try something longer"); // The compiler doesn’t care.

Needless to say this should not be used: it might give the user a false sense of security (“oh, this function only accepts an array of length 10 so I don’t need to check the length myself”).

As Henrik said, the correct way in C++ is to use a strongly typed buffer. Depending on your use-case and the actual meaning of the parameter, this could either be a string type (e.g. std::string1 or std::string_view), a byte buffer (std::vector<char>1 or an iterator-delimited range) or a fixed-width range or slice (std::array<char, N>, std::span<char, N>).


1 usually as a (const) reference

Sign up to request clarification or add additional context in comments.

15 Comments

This bit me when I tried to use boost::begin() on a function with sized array as parameter. Doesn't work. Was just pointer.. meh.
@Macke If that’s still relevant have a look at Morwenn’s answer, you can pass fixed-sized arrays to a function, albeit only by reference (but that should never be a problem).
might be useful to cite the standard: C99 standard 6.7.5.3, para. 7, "A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type" ..."
@pmttavara We're talking about non string literal situations here. Otherwise by all means use a char const* (or pass an array by reference, i.e. char (&arr)[N], where N is a non type template argument). In most other situations you either incur no allocation or the cost is negligible. Working on stack allocated char buffers is very rarely a good idea.
@RickyGonce You could pass an array by reference (e.g. func3(char const (&x)[10])), but this only works for arrays that have exactly the right size. And is the size of the array really relevant? Why not take a strongly-typed fixed- or variable-sized buffer/string instead (std::span/std::string_view/…)? That’s almost always the more appropriate strategy.
|
29

Note that in C++, if the length of the array is known at compile time (for example if you passed a string literal), you can actually get its size:

template<unsigned int N>
void func(const char(&str)[N])
{
    // Whatever...
}

int main()
{
    func("test"); // Works, N is 5
}

4 Comments

This will make a (tweaked) copy of func machine code for each new length of the literal passed.
@Ruslan True, yet I would expect any compiler to inline that at even the lowest optimization level.
Depends on the size of // Whatever....
@Ruslan Oh right, I was just thinking of a function to get the size of an array and forgot the scope of the question was broader :/
12

In C++, use void func4(const std::string& str).

2 Comments

Or void func4( std::vector<char> const& str ).
12

These are all functionally identical. When you pass an array to a function in C, the array gets implicitly converted to a pointer to the first element of the array. Hence, these three functions will print the same output (that is, the size of a pointer to char).

void func1(char* str) {
    printf("sizeof str: %zu\n", sizeof str);
}

void func2(char str[]) {
    printf("sizeof str: %zu\n", sizeof str);
}

void func3(char str[10]) {
    printf("sizeof str: %zu\n", sizeof str);
}

This conversion only applies to the first dimension of an array. A char[42][13] gets converted to a char (*)[13], not a char **.

void func4(char (*str_array)[13]) {
    printf("sizeof str_array: %zu\n"
           "sizeof str_array[0]: %zu\n", sizeof str_array, sizeof str_array[0]);
}

char (*)[13] is the type of str_array. It's how you write "a pointer to an array of 13 chars". This could have also been written as void func4(char str_array[42][13]) { ... }, though the 42 is functionally meaningless as you can see by experimenting, passing arrays of different sizes into func4.

In C99 and C11 (but not C89 or C++), you can pass a pointer to an array of varying size into a function, by passing it's size along with it, and including the size identifier in the [square brackets]. For example:

void func5(size_t size, char (*str_array)[size]) {
    printf("sizeof str_array: %zu\n"
           "sizeof str_array[0]: %zu\n", sizeof str_array, sizeof str_array[0]);
}

This declares a pointer to an array of size chars. Note that you must dereference the pointer before you can access the array. In the example above, sizeof str_array[0] evaluates to the size of the array, not the size of the first element. As an example, to access the 11th element, use (*str_array)[11] or str_array[0][11].

Comments

2

In C, the first two definitions are equivalent.The third one is essentially same but it gives an idea about the size of the array.

If printing str is your intent, then you can safely use any of them.Essentially all three of the functions are passed a parameter of type char*,just what printf() needs to print a string.And lest you don't know, despite what it may seem, all parameter passing in C is done in pass-by-value mode.

Edit: Seems like I'll have to be very rigorous in my choice of words on SO henceforth.Well,in the third case it gives no idea about the size of the array to the function to which it is passed as eventually it is reduced to type char* just as in the first two cases.I meant to say it kinda tells the human reading it that the array's size is 10.Also,it is not wrong/illegal in C.But for the program,doing it is as good as useless.It gives no idea whatsoever about the array size to the function it is passed to.Mr.Downvoter, thanks for pointing out that casual attitude and negligence is not tolerated on SO.

2 Comments

It gives a bad idea about the size of the array.
@LightnessRacesinOrbit I would never use the third syntax in any case.Just pointless to use that.
1

In a one dimensional array they are all treated the same by the compiler. However for a two or more dimensional array, (e.g. myArray[10][10]), it is useful as it can be used to determine the row/column length of an array.

1 Comment

No, the third function does not pass an array of a fixed size. This would imply that there is a difference between those functions but there really is none. The type of all three functions is the same. If you want to provide a hint as to the expected array size, use a comment or documentation, that makes it clear that the size isn’t checked by the compiler.
1

To add-on, describing in points.

1) As everyone told it is same.

2) Arrays are decayed into pointers when they are passed in the function arguments.

3) Fundamental problem could be finding the size of a array in the function. For that we can use macro like.

   #define noOfElements(v) sizeof(v)/sizeof(0[v])

   int arr[100]
   myfunction ( arr, noOfElements(arr))

either 0[v] or v[0] can be used in the macro, where the first is used to avoid user defined data type passed in to noOfElements.

Hope this helps.

3 Comments

It should be v[0] in the macro you defined there.
v[0], can be written as 0[v], actually the conversion happens like this *(v+0) and *(0+v) respectively.
If you are using a macro, even better to define a macro to give you the arr pointer and size where you only have to write arr once (to avoid copy and paste errors). For example, #define ARRAY_WITH_SIZE(x) x,sizeof(x). Then you can call myfunction(ARRAY_WITH_SIZE(arr)); - You could also define ARRAY_WITH_LENGTH by using the same concept you used above.
0

In modern C++, you should use std::string_view (C++17) or std::span (C++20), which know the size of the array and provide more compile-time and runtime checking.

What is string_view?
What is a "span" and when should I use one?

void f1(std::string_view str) {
    std::cout << str << std::endl;
}
void f2(std::span<const char> str) {
    f1({ str.data(), str.size() }); //print str
}
void f3(std::span<const char, 10> str) {
    f1({ str.data(), str.size() }); //print str
}
int main() {
    f1("foo");
    const char* str = "garbage bar garbage";
    f2({ str + 8, 3 }); // print "bar"
    f2("foobar");
    f3("123456789"); // construct std::span<const char, 10> from const char[10]

    f3("array too long or short"); // Compile Error: can't not construct std::span<const char, 10> from const char[23]

    std::vector<char> vec{ 'v','e','c' };
    f2(vec);
    f2({ vec.begin(), vec.end() });
    f2({ vec.data(), vec.size() });

    // Because the constructor is marked as explicit, the constructor must be called explicitly.
    // At runtime, the size may, but may not, be checked.
    f3(std::span<const char, 10>{ vec });
    f3(std::span<const char, 10>{ vec.begin(), vec.end() });
    f3(std::span<const char, 10>{ vec.data(), vec.size() });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.