78

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?

Morwenn
  • 21,684
  • 12
  • 93
  • 152
mr5
  • 3,438
  • 3
  • 40
  • 57

7 Answers7

95

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"); // Not a single f*ck given.

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 std::string, std::string& or std::string const& (depending on whether you need to modify the object, and whether you want to copy).

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 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 Apr 22 '13 at 13:47
  • @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). – Konrad Rudolph Apr 22 '13 at 14:47
  • 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" ..." – newacct Apr 22 '13 at 18:22
  • Why is using `std::string` the *correct* way? It is no more correct or less correct that using pointers. There is a difference between *typical/standard* and *correct*. – Thomas Eding Apr 22 '13 at 18:44
  • @ThomasEding The only relevant difference is in terms of complexity, efficiency, and how easy it is to make mistakes. In all those regards `std::string` wins (in terms of efficiency this needs to be prefixed by “often but not always”). Using `char*` for strings is (almost) never appropriate in C++. – Konrad Rudolph Apr 22 '13 at 19:39
  • `std::string` loses. Incurring a heap allocation just to pass a string is bad practice. I suggest `std::string_view`, an equivalent struct of your choosing, or simply `func(char *str, size_t len)`. – pmttavara Jan 19 '18 at 21:28
  • 1
    @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. – Konrad Rudolph Jan 19 '18 at 21:33
  • In cases that allocate, the cost is very non-negligible. Even ignoring the overhead of allocating, you fragment the heap, which is terrible. Templating on string length is bad, since the duplicate code bloats the binary, thrashes the icache in debug mode, and overworks (or even defeats) the optimizer in release mode. Lastly, using references as in `const std::string &` adds another pointer indirection which fosters cache misses and extra instructions (at least in debug mode, where cycles are the most precious). I *highly* suggest a string view structure passed by value. – pmttavara Jan 20 '18 at 02:56
  • @pmttavara I’m not sure where you’re getting these ideas. In fact, by all means use a string view or an equivalent type (especially since `string_view` isn’t yet widely supported). But you are seriously overestimating the negative impact of allocations in common scenarios. Your comment is redefining premature optimisation. – Konrad Rudolph Jan 20 '18 at 18:50
  • @pmttavara I’m not sure where you’re getting these ideas. In fact, by all means use a string view or an equivalent type (especially since `string_view` isn’t yet widely supported). But you are seriously overestimating the negative impact of allocations in common scenarios. Your comment is practically redefining premature optimisation. – Konrad Rudolph Jan 20 '18 at 18:51
  • Ignoring those issues is to deny the facts, which you do at your peril. Knowing what is and is not slow at the beginning of a project becomes indispensable once you begin refining the program into high-quality software that runs fast. – pmttavara Feb 19 '18 at 11:36
  • I'm wondering that nobody recommends C++ new std::array class here. If i want to pass a fixed-array, i would always prefer std::array. If you need to pass it by reference, use "const std::array&". This is imo much more consistent in C++ world. (Of course, string with its "small string optimization" or string_view is also a good option. Depends on the code + requirements). – ruuns Apr 21 '21 at 20:00
  • @ruuns `std::array` works well for smallish buffers, but it’s not often convenient to use with strings since you can’t construct them from string literals “on the fly” (i.e. as a temporary inside an expression, or in an initialiser without repeating the literal). There are also precious little cases where you’d *need* to pass a char buffer of hard-coded size to a function. I can remember *one* case where I needed this, and I explicitly needed to support passing string literals, so I instead passed a C-style char array by const reference. – Konrad Rudolph Apr 21 '21 at 21:02
  • Is there a way to rewrite this function such that an overflow would be caught by the compiler? – Ricky Gonce Oct 06 '21 at 13:27
  • 1
    @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. – Konrad Rudolph Oct 06 '21 at 14:02
27

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
}
Morwenn
  • 21,684
  • 12
  • 93
  • 152
12

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

Henrik
  • 23,186
  • 6
  • 42
  • 92
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].

autistic
  • 1
  • 3
  • 35
  • 80
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.

Rüppell's Vulture
  • 3,583
  • 7
  • 35
  • 49
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.

user2301717
  • 1,049
  • 1
  • 10
  • 13
  • 3
    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. – Konrad Rudolph Apr 22 '13 at 10:40
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.

Whoami
  • 13,930
  • 19
  • 84
  • 140
  • It should be v[0] in the macro you defined there. – Sai Nikhil Nov 07 '14 at 07:13
  • 2
    v[0], can be written as 0[v], actually the conversion happens like this *(v+0) and *(0+v) respectively. – Whoami Jan 14 '15 at 07:00
  • 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. – Wayne Uroda Apr 12 '16 at 06:41