(my trouble with) Passing Arrays by array type/pointer type

Continuing my off-topic question from this topic, post #21... Help with PROGMEM and char arrays - #21 by xfpd

@J-M-L @david_2018 - I did not express my trouble with arrays and pointers, but I think it is time to ask "how" as I am using not-so-clean ways to make argument available in different functions (x/y/z locations and r/g/b colors). So, here is the code that I have been using to try to understand (original is from @J-M-L, I believe), but my comprehension of the terms used lacks. Any "pointers" welcome.

The code...

// https://arduinogetstarted.com/faq/how-to-pass-array-to-function-in-arduino
// https://academy.programmingelectronics.com/pass-array-to-function-in-arduino/

int array_1[] = { 1, 2 };
int length_1 = sizeof(array_1) / sizeof(array_1[0]);
int array_2[] = { 3, 4, 5, 6 };
int length_2 = sizeof(array_2) / sizeof(array_2[0]);

byte thisOne = 0;

void setup() {
  Serial.begin(115200);

  if (thisOne == 0) {
    // pass by ARRAY type
    passByArrayType(array_1, length_1);
    passByArrayType(array_2, length_2);
  } else {
    // pass by POINTER type
    int pointer_1 = &array_1;                // can not pass &array_1 "warning: invalid conversion from 'int' to 'int*'"
    int pointer_2 = &array_2;                // can not pass &array_2 "warning: invalid conversion from 'int' to 'int*'"
    passByPointerType(pointer_1, length_1);  // pass the address of array_1 // warning: invalid conversion from 'int' to 'int*' [-fpermissive]
    passByPointerType(pointer_2, length_2);  // pass the address of array_2 // warning: invalid conversion from 'int' to 'int*' [-fpermissive]
  }
}

void loop() {
}

void passByArrayType(int myArray[], int length) {
  Serial.print("ARRAY");
  for (byte i = 0; i < length; i++) {
    Serial.print(myArray[i]);
  }
  Serial.println();
}

void passByPointerType(int* myPointer, int length) {  // note: initializing argument 1 of 'void passByPointerType(int*, int)'
  Serial.print("POINTER");
  for (byte i = 0; i < length; i++) {
    Serial.print(*(myPointer + i));
  }
  Serial.println();
}

The error(s)...

C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-13032-6hfrg1.hfp9j\sketch_feb11a\sketch_feb11a.ino: In function 'void setup()':
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-13032-6hfrg1.hfp9j\sketch_feb11a\sketch_feb11a.ino:20:21: error: invalid conversion from 'int (*)[2]' to 'int' [-fpermissive]
   20 |     int pointer_1 = &array_1;                // can not pass &array_1 "warning: invalid conversion from 'int' to 'int*'"
      |                     ^~~~~~~~
      |                     |
      |                     int (*)[2]
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-13032-6hfrg1.hfp9j\sketch_feb11a\sketch_feb11a.ino:21:21: error: invalid conversion from 'int (*)[4]' to 'int' [-fpermissive]
   21 |     int pointer_2 = &array_2;                // can not pass &array_2 "warning: invalid conversion from 'int' to 'int*'"
      |                     ^~~~~~~~
      |                     |
      |                     int (*)[4]
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-13032-6hfrg1.hfp9j\sketch_feb11a\sketch_feb11a.ino:22:23: error: invalid conversion from 'int' to 'int*' [-fpermissive]
   22 |     passByPointerType(pointer_1, length_1);  // pass the address of array_1 // warning: invalid conversion from 'int' to 'int*' [-fpermissive]
      |                       ^~~~~~~~~
      |                       |
      |                       int
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-13032-6hfrg1.hfp9j\sketch_feb11a\sketch_feb11a.ino:38:29: note:   initializing argument 1 of 'void passByPointerType(int*, int)'
   38 | void passByPointerType(int* myPointer, int length) {  // note: initializing argument 1 of 'void passByPointerType(int*, int)'
      |                        ~~~~~^~~~~~~~~
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-13032-6hfrg1.hfp9j\sketch_feb11a\sketch_feb11a.ino:23:23: error: invalid conversion from 'int' to 'int*' [-fpermissive]
   23 |     passByPointerType(pointer_2, length_2);  // pass the address of array_2 // warning: invalid conversion from 'int' to 'int*' [-fpermissive]
      |                       ^~~~~~~~~
      |                       |
      |                       int
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-13032-6hfrg1.hfp9j\sketch_feb11a\sketch_feb11a.ino:38:29: note:   initializing argument 1 of 'void passByPointerType(int*, int)'
   38 | void passByPointerType(int* myPointer, int length) {  // note: initializing argument 1 of 'void passByPointerType(int*, int)'
      |                        ~~~~~^~~~~~~~~
exit status 1

Compilation error: invalid conversion from 'int (*)[2]' to 'int' [-fpermissive]

The code on Wokwi does not create an error and both "by array" and "by pointer" produce the expected result:

POINTER 12
POINTER 3456

Every few months I have a go at re-writing this to stop it from producing warnings, but then walk the dog to be more productive.

I have been flipping-through some documents: The C Book — Pointers ... but nothing yet that I understand enough to use. In the mean time, I "solve" my passing problem with globals and not-so-clean means. Thank you for any "pointers."

The "C++ Way" is to use References:

Main .ino:

#include "PassByReference.h"

void setup() {
  Serial.begin(115200);
  delay(1000);

  int array_1[] = { 1, 2 };
  int array_2[] = { 3, 4, 5, 6 };

  passByArrayReference(array_1);
  passByArrayReference(array_2);
}

void loop() {
}

PassByReference.h:

#ifndef PBR
#define PBR

#include <Arduino.h>
template<size_t N>
void passByArrayReference(int (&myArray)[N]) {
    Serial.print("ARRAY Length:");
    Serial.println(N);
    for(auto x:myArray) {
      Serial.print(x);
      Serial.print("  ");
    }
    Serial.println();
}

#endif

Output:

ARRAY Length:2
1  2  
ARRAY Length:4
3  4  5  6 

Use of the .h file is necessitated by the Arduino IDE's stupid auto-function prototype generator (at least Arduino IDE 1.8.x). It wouldn't be required with a more sophisticated C++ IDE.


    int pointer_1 = &array_1;                // can not pass &array_1 "warning: invalid conversion from 'int' to 'int*'"
    int pointer_2 = &array_2;                // can not pass &array_2 "warning: invalid conversion from 'int' to 'int*'"


    int * pointer_1 = &array_1;                // can not pass &array_1 "warning: invalid conversion from 'int' to 'int*'"
    int * pointer_2 = &array_2;                // can not pass &array_2 "warning: invalid conversion from 'int' to 'int*'"

@gfvalvo

I see this causes no warnings... I will compare it to my flawed sketch.

No, you'd need:

  int * pointer_1 = array_1;
  int * pointer_2 = array_2;

Because &array_1 is a pointer to an array of int with two elements. On the other hand, array_1 will decay to a pointer-to-int (pointing to the first int in the array).

@LarryD
I inserted the pointer/asterisk and received these errors...

C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-2640-1kf2n4s.qvhp\sketch_feb11a\sketch_feb11a.ino: In function 'void setup()':
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-2640-1kf2n4s.qvhp\sketch_feb11a\sketch_feb11a.ino:20:23: error: cannot convert 'int (*)[2]' to 'int*' in initialization
   20 |     int * pointer_1 = &array_1;                // can not pass &array_1 "warning: invalid conversion from 'int' to 'int*'"
      |                       ^~~~~~~~
      |                       |
      |                       int (*)[2]
C:\Users\user\AppData\Local\Temp\.arduinoIDE-unsaved2026111-2640-1kf2n4s.qvhp\sketch_feb11a\sketch_feb11a.ino:21:23: error: cannot convert 'int (*)[4]' to 'int*' in initialization
   21 |     int * pointer_2 = &array_2;                // can not pass &array_2 "warning: invalid conversion from 'int' to 'int*'"
      |                       ^~~~~~~~
      |                       |
      |                       int (*)[4]
exit status 1

Compilation error: cannot convert 'int (*)[2]' to 'int*' in initialization

Similar "cannot convert" at the end of the error. I have been trying to understand int* pointer versus int * pointer versus int *pointer

There is no difference. Just a visual difference for you as a programmer.
Also: array is not a type. std::vector is a type (but not supported on all arduino boards).
See also post #5.

This works in the original sketch, without warnings.

Thank you. Time for me to read how and why with more understanding.

The trick is that an array name is a pointer to the first element of that array.
The [i] tells the compiler to add i times the number of bytes per element to this pointer and to get the value that is written there. Therewith it returns the i-th element.
&a[0] (the address of the first element of array a) is equal to a itself...

Yes, this makes sense in reading, but then I fall flat when the training wheels are off... soon... I'm still reading.

Did the IDE "verbose" option get less strict? In post #1, the sketch, under // pass by POINTER type the comments that follow were the error reported from a "verbose" compile in IDE (maybe 1.8, maybe 2.x)... in particular the lines beginning passByPointerType() with warning: invalid conversion "int" to "int*"... Now, I get no warnings or errors like that for these two lines in question. I am using IDE 2.3.7 with verbose turned on.

Thank you.

Verbose tells the compiler to add some explaining text to the errors and warnings.
It does not tell how strict it is.
That is another flag that can be set via settings.
Some warnings may be treated as errors if set to strict. Use this setting, or at least take every warning serious. Basically it warns you that you are interpreting an integer as an address in memory. And since an address is also a 2 byte number, this can be done. But it will not do what you want and cause very strange results (like crashes or random results).

This might help you:

Thank you. My warnings are set to "All"

That should be the most strict. So leave it like that!

This might help; may require IDE 2.x: inside setup, add

  auto what_is_this = array_2;

Let it churn momentarily. You might see "indexing" in the bottom left corner. Then hover over array_2, it will say

The Type says it's an array of four ints. Hover over the variable

Assigning the variable, it has decayed to a pointer, to the first one, element zero. Now add the &

  auto what_is_this = &array_2;

and hover over the variable again. It is

At this point, the utility program cdecl can help

$ cdecl
Type `help' or `?' for help
cdecl> explain int foo[4]
declare foo as array 4 of int
cdecl> explain int (*foo)[4]
declare foo as pointer to array 4 of int
cdecl> explain int *foo[4]
declare foo as array 4 of pointer to int

Here it's that second one, which should make the error messages clearer.

If you want pure high test stuff to mainline, you may find K&R's exposure on the topic to be worth the time it takes.

The intimate relationship between arrays and pointers is important to understand so you can read and write this kind of code.

Too many actual real programmers haven't, really, and throw asterisks and ampersands around until things work. Srsly, I have overheard ppl say that out loud.


→ Chapter 5. Read it until all the pieces fall into place.

TBH, once you can code your way out of a paper bag, K&R (the Bible) is a book still worth working through. My original copy is in tatters, it was what I had to grab to refresh my memory on things.

a7

Well, that's true, but I would normally prefer

int *pointer

because from the compilers point of view the '*' belongs to the variable name and not to the type. This can be very misleading when you type

int* pointer1, pointer2;

In this case, the compiler treats only pointer1 as a pointer, pointer2 is simply an int variable.

I just see me mentioned at the start of the thread, but you got lots of good input along the way, so not sure I need to chime in but for the sake of it:

When you define an array in C++, the compiler allocates consecutive bytes in memory. The number of bytes depends on how many bytes are required for 1 element of the selected type multiplied by the number of elements you want in the array.
The size can't be 0 and if you omit the size in the definition but provide a list of initial values, then the compiler will calculate the number of elements for you.

For example you have 3 elements in both arrays

int array1[3];
int array2[] = {1, 2, 3};

In both C and C++, array1 and array2 have the type int[3]. The second declaration uses size deduction from the initializer, but the resulting type is still an array of three integers.

When you start using the name of the array though, in most expressions, the array name decays to a pointer to its first element of type int*, yet the array itself is not a pointer.

Array-to-pointer decay does not occur in well-defined contexts for example with the sizeof operator. When you do

size_t totalBytesForArray1 = sizeof array1;

Here the operand of sizeof is an array expression, and no decay happens. You don't get the size of a pointer, you really get the number of bytes in the array.

Said differently, array1 is an object of type int[3]. Because it is truly an array object and not a pointer, sizeof array1 is evaluated using the actual array type. The compiler knows at compile time that the array contains three int, so sizeof array1 is equal to 3 * sizeof(int) and since no array-to-pointer decay occurs in the operand of sizeof, this expression reliably gives the total storage size of the array.

Dividing by sizeof array1[0] as you did yields the number of elements. This works only in the same scope where the array type is still known. Once the array has decayed to a pointer, that information is lost. This is why if you call sizeof on an array name that has decayed (say inside a function) you get the size of a pointer for your MCU architectures (2 or 4 bytes on our common Arduinos) and no longer the total number of bytes in the array.

Another example where no decays happens is when you use &array1. This yields a value of type int (*)[3], that is, a pointer to an array of three integers. If decay had occurred first, you would instead get an int**, which is not the case.


Now consider a function:

void func(int *arrayPtr, size_t nbElements) {}

When you call func(array1, 3); the array expression array1 undergoes array-to-pointer decay. It is implicitly converted to a value of type int* pointing to the first element, that is &array1[0]. The function receives a pointer to the first int.

If instead you write func(&array1, 3); then &array1 has type int (*)[3], which is a pointer to an array of three int. This is not the same type as int*. However, in C and C++, a pointer to an array object can be converted to int* via an implicit conversion that effectively adjusts the address to that of the first element, because both point to the same starting memory location. The numerical address value is the same as &array1[0], but the static type is different before conversion.

In practice, both calls result in the function receiving a pointer to the first element of the array. The key difference is that array1 decays to int* automatically, while &array1 first produces a pointer-to-array type and then is implicitly converted to match the parameter type.

See below I assumed the implicit transform would be there but it’s not.

Hope this helps and makes sense.

Word. Your point bears repeating and amplification.

I remembered the concept, but not the words, so I asked AI for help.

My prompt

I need an exact quote, or say it well yourself, that teaches the beauty of C declarations, something like "use mimics declaration". int *x; What does *x = 42; do? We start by seeing that *x is… an int. It's one of the strengths. Once you understand declarations you can read uses.

And rather than pick one of its several suggestions to inform my post here, I decided to just post the full response, in the hope that one or more of them will capture the reader's imagination and take hold:

Over to… chatGPT:

I just told it what I was going to do.

Srsly, this is one of those things you can read right past wherever it is written, but is so useful for making and using data types easier.

Declarations can get complicated, as can use of complex data types. A programmer in at this end of the spectrum at the very least has to be able to sling multi-dimensional arrays and pointers, structs and pointers to them and be at least able to see how pointers to functions work.

a7

Well that’s of course an issue if you don’t have a real int to point at….

Just writing

int *x; 
*x = 42;

Is a bug you need memory allocated you can point at

int data;
int *x; 
x = &data;
*x = 42; // data now holds 42