1

So I understand arrays decay to pointers but isn't there an effective way of computing the array size particularly with compound literals?

say I am passing array of different sizes to a function; ideally i'd use an approach that determines the size dynamically without me having to hardoce the numbers but I can't do that inside the function to begin with. The other option left is the caller code, in which I can't really change static arrays for each function call

foo((uint8_t) {1,2});
foo((uint8_t) {1});
xyf
  • 664
  • 1
  • 6
  • 16
  • I don't think so. declare the arrays outside of the function call, and pass in sizeof()? – OldProgrammer Jun 30 '20 at 02:04
  • Pardon, if I didn't understand. You want to pass an array and don't want to mention its size each time you call the function. You don't want this `foo( int arr[5] )`. – Shubham Jun 30 '20 at 03:30

3 Answers3

1

A variant on Chris Dodd's suggestion is to wrap your function in a macro:

void real_foo(uint8_t *arr, size_t len);

#define foo(...) real_foo((uint8_t[]) __VA_ARGS__, (sizeof (uint8_t[]) __VA_ARGS__)/sizeof(uint8_t));

// ...

foo({1});
foo({1,2});

Try on godbolt

Note the use of a variadic macro to work around the fact that {1,2} is parsed as two macro arguments.

For slightly different syntax, you could also write { __VA_ARGS__ } in the macro definition, so that you can invoke it as

foo(1);
foo(1,2);

You can also macro-ize supercat's solution, if you're willing to use gcc/clang statement expressions (a non-standard C extension):

#define foo(...) ({ static const uint8_t my_array[] = { __VA_ARGS__ }; real_foo(my_array, sizeof(my_array) / sizeof(uint8_t)); })

Or if real_foo() returns void, you can do the following which is standard C:

#define foo(...)  do { static const uint8_t my_array[] = { __VA_ARGS__ }; real_foo(my_array, sizeof(my_array) / sizeof(uint8_t)); } while (0)

where the do/while will eat a semicolon.

This of course assumes that real_foo() doesn't need to modify the array. If it does, this won't work, even without the const, since the array won't be re-initialized if the code is executed again.

Another option is to redesign your function so that it doesn't need to be told the size of the array. For instance, if there is some value that otherwise isn't valid as an entry of the array, you could use it as a sentinel:

foo((uint8_t []){1, 2, 0xff});
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
1

The the approach I'd suggest would be to declare a named object using standard initializer syntax.

static const uint8_t my_array[] = {1,2};

whereupon you can pass my_array and sizeof my_array. Using compound literals may seem more convenient, but unless they are small it will degrade efficiency because there is no way to create const-qualified compound literals with static duration. Creating objects with automatic duration and passing their address to outside code would generally make it necessary for a conforming compiler to actually create and populate the objects every time they're used, even if they're declared const, while using static-duration const-qualified objects avoids such issues.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Assuming, of course, that `foo` can accept a `const` array. It might expect to be able to modify the array. – Nate Eldredge Jun 30 '20 at 21:40
  • But is there a reason why the compiler has to treat compound literals this way? Why can't it create a static array with the desired contents, and avoid the copying? It seems to me that the effect would be exactly the same. – Nate Eldredge Jun 30 '20 at 21:52
  • @NateEldredge: If `foo()` creates a compound literal and passes its address to `bar()`, which stores the pointer in some static location and recursively calls `foo()`, and that recursive call to `foo()` re-executes the code that created the literal and called `bar()`, and this second call to `bar()` compares the second passed-in address to the statically stored one, the overlapping lifetimes of the two compound literals would imply that they would have different addresses. – supercat Jun 30 '20 at 22:04
  • Ah, sneaky. Thanks. – Nate Eldredge Jun 30 '20 at 22:06
  • @NateEldredge: If the Standard were to specify that storage for const-qualified compound literals whose contents will never change during program execution may, at a compiler's leisure, be shared with strings or any other such compound literals, I doubt any non-contrived programs would be adversely affected, but the Standard does not presently allow such optimizations. – supercat Jun 30 '20 at 22:16
  • the reason I used compound literals was that it's easier for me to just create an array within arguments and pass it right away instead of creating an array for each function call which may have different array elements/size – xyf Jul 01 '20 at 03:47
  • @xyf: One of the things I dislike about many of the features that were added in C99 is that they make it more convenient to do things in ways that force compilers to generate inefficient code than to do things in ways that can be processed efficiently. – supercat Jul 01 '20 at 18:24
  • i'm not sure how using compound literals make things inefficient – xyf Jul 01 '20 at 18:55
  • @xyf: I explained that in the answer. Passing the address of a compound literal in a scenario where one could have passed a static-const object will generally force a compiler to generate code to populate the object every time it's used. – supercat Jul 01 '20 at 20:18
  • but you would still have to create multiple arrays for each function call even if you use `static`, no? – xyf Jul 01 '20 at 20:22
  • @xyf: In the scenario were all of the items of an array are compile-time or link-time constants, the array can be built at compile or link time. If there are twelve function calls that all use different static constant arrays, it would be necessary to build twelve arrays at compile time or link time, but none of them would need to be built when calling the functions. Unless the arrays were large and had a lot of repeated data, storing the data for each array would generally cost less than including the code necessary to build it each time it's used. – supercat Jul 01 '20 at 20:35
  • sorry i'm still not certain as to how does creating 12 static const arrays is different than passing 12 different arrays to a function via compound literals except for a fact that compound literals aren't const static but why would that matter? – xyf Jul 01 '20 at 22:44
  • @xyf: If `foo()` calls `bar( (unsigned char[]) {1,2} );`, then until `bar` returns, the address of that array is guaranteed not to be shared with anything else. Even if a recursive call to `foo` makes a nested call to `( (unsigned char[]) {1,2} );`, the Standard specifies that the nested call would receive a different array, which would have to be created at that time. When using a static const array, there would be no need to create the array at runtime; it would simply exist for the lifetime of the program. – supercat Jul 02 '20 at 03:47
0

Your example is not valid C syntax, though gcc accepts it with the warning

warning: excess elements in scalar initializer

and then proceeds to pass just the first value in the braces as an argument. You could do this by adding a [], but then you lose the size, as you note:

foo((uint8_t[]) {1,2});

You can compute the size and pass it as a second argument, but that requires duplicating the static array:

foo((uint8_t[]) {1,2}, sizeof((uint8_t[]) {1,2})/sizeof(uint8_t));

which may be acceptable if you define it as a macro or variable:

#define ARR (uint8_t[]) {1,2}
      // or
uint8_t ARR[] = {1, 2};

foo(ARR, sizeof(ARR)/sizeof(ARR[0]));
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Yeah I missed the `[]`. But like I mentioned, the issue with defining an array as a variable is I don't want to be doing that for each function call considering the situation where each function call takes in different array elements/size – xyf Jun 30 '20 at 21:05