Passing array of structures to function c++
Asked Answered
G

6

20

Sorry for the noob question I'm just a little confused.
If I have an array of structures in main that I want to pass to a function:

struct MyStruct{
    int a;
    int b;
    char c;
    mayarray[5];
};  
MyStruct StructArray[10]; 

myFunction(StructArray[])

Pass to this to a function:

void myFunction(struct MyStruct PassedStruct[])
{
    PassedStruct[0].a = 1;
    PassedStruct[0].b = 2;
    // ... etc
}  

My question is, will calling the function like this modify the data in StructArray? I need it to. Would that be call by reference? I'm a little confused. How would I change it so that when I pass the array of structures to the function, the function will modify the array StructArray? I'musing visual studio btw.
Thanks.

Gnomon answered 31/8, 2010 at 21:18 Comment(2)
No need to apologise, stackoverflow is for questions at all skill levels, particularly when they are as clearly asked as this one is :)Ogham
did anyone in an answer end up mentioning that you can also tack on a '&' after the param type and use c++ call by reference feature explicitly? (I don't do this often because it takes away the added information of a de-reference)Hammerskjold
M
32
struct MyStruct PassedStruct[]

is mostly an alternative syntax for:

struct MyStruct * PassedStruct

So yes, you will access and modify the original structure.

Just one detail to change, the correct call to to the function is not

myFunction(StructArray[]);

but:

myFunction(StructArray);

Now I will try to explain why I used the word mostly in the above sentence:

I will give some hint at the difference between arrays and pointers, why you shouldn't confuse them (even if I would not say they are unrelated, quite the opposite), and the problem with the above MyStruct PassedStruct[] parameter passing syntax.

This is not for beginners, and C++ standard experts should also avoid reading this (because I don't want to get in some - ISO Standard_ war as I enter ISO undefined behavior territory - a.k.a. forbidden territory).

Let's begin with arrays:

Imagine a simple structure:

struct MyStruct{
    int a;
    int b;
    char c;
};  

MyStruct a1[3]; is the declaration of an array whose items are of the above structure type. The most important thing the compiler does when defining an array is to allocate space for it. In our example it reserved space for 3 structs. This reserved space can be on stack or from global memory resources depending where the declaration statement is.

You can also initialize the struct when declaring it like in:

struct MyStruct a1[3] = {{1, 2}, {3, 4}, {5, 6}};

Notice that in this example, I didn't initialize the c field but just a and b. This is allowed. I could also use designator syntax if my compiler supports it like in:

struct MyStruct a1[3] = {{a:1, b:2}, {a:3, b:4}, {a:5, b:6}};

Now, there is another syntax for defining an array using empty square backets like in:

struct MyStruct a2[] = {{1, 2}, {3, 4}, {5, 6}};

The point here is that a2 is a perfectly normal array just like a1. The array size is not implicit, it is given through the initializer: I have three initializers therefore I get an array of three structs.

I could define an uninitialized array of known size with this syntax. For an uninitialized array of size 3 I would have:

struct MyStruct a2[] = {{},{},{}};

Space is allocated, exactly as with the previous syntax, no pointer involved here.

Let's introduce one pointer:

MyStruct * p1;

This is a simple pointer to a structure of type MyStruct. I can access fields through usual pointer syntax p1->a or (*p1).a. There is also an array flavored syntax to do the same as above p1[0].a. Still the same as above. You just have to remember that p1[0] is a shorthand for (*(p1+0)).

Also remember the rule for pointer arithmetic: adding 1 to a pointer means adding the sizeof the pointed object to the underlying memory address (what you get when you use %p printf format parameter). Pointer arithmetic allows access to successive identical structs. That means that you can access structs by index with p1[0], p1[2], etc.

Boundaries are not checked. What is pointed to in memory is the programmer's responsibility. Yes, I know ISO says differently, but that is what all compilers I ever tried do, so if you know of one that does not, please tell me.

To do anything useful with p1, you have to make it point to some struct of type MyStruct. If you have an array of such structs available like our a1, you can just do p1=a1 and p1 will point to the beginning of the array. In other words you could also have done p1=&a1[0]. It's natural to have a simple syntax available as it's exactly what pointer arithmetic is designed for: accessing arrays of similar objects.

The beauty of that convention is that it allows to completely unify pointer and array access syntax. The difference is only seen by the compiler:

  • when it sees p1[0], it knows it has to fetch the content of a variable whose name is p1 and that it will contain the address of some memory structure.

  • when it sees a1[0], it knows a1 is some constant that should be understood as an address (not something to fetch in memory).

But once the address from p1 or a1 is available the treatment is identical.

A common mistake is to write p1 = &a1. If you do so the compiler will give you some four letter words. Ok, &a1 is also a pointer, but what you get when taking the address of a1 is a pointer to the whole array. That means that if you add 1 to a pointer of this type the actual address will move by steps of 3 structures at once.

The actual type of a pointer of that kind (let's call it p2) would be MyStruct (*p2)[3];. Now you can write p2 = &a1. If you want to access the first struct MyStruct at the beginning of the memory block pointed to by p2 you will have to write somethings like p2[0][0].a or (*p2)[0].a or (*(*p2)).a or (*p2)->a or p2[0]->a.

Thanks to type system and pointer arithmetic all of these are doing exactly the same thing: fetch the address contained in p2, use that address as an array (a known constant address) as explained above.

Now you can understand why pointers and arrays are totally different types that should not be confused as some may say. In plain words pointers are variable that contains an address, arrays are constant addresses. Please don't shoot me C++ Gurus, yes I know that is not the full story and that compilers keep many other informations along with address, size of pointed (addressed ?) object for example.

Now you could wonder why in parameter passing context you can use empty square brackets and it really means pointer. ? No idea. Someone probably thought it looked good.

By the way, at least with gcc, you can also put some value between brackets instead of keeping them empty. It won't make a difference you'll still get a pointer, not an array, and boundaries or type checking are not done. I didn't checked in ISO standard was should be done and if it is required by the standard or if it is a specific behavior.

If you want type checking for boundaries, just use a reference. That may be surprising but this is an area where if you use a reference, the actual type of the parameter is changed from pointer to array (and not from pointer to reference to pointer as may be expected).

MyStruct StructArray[10]; 
  • header: void myFunction(struct MyStruct * PassedStruct)
  • caller: myFunction(StructArray)
  • status: works, you work with a pointer in PassedStruct
  • header: void myFunction(struct MyStruct PassedStruct[])
  • caller: myFunction(StructArray)
  • status: works, you work with a pointer in PassedStruct
  • header: void myFunction(struct MyStruct (& PassedStruct)[10])
  • caller: myFunction(StructArray)
  • status: works, you work with a reference to an array of size 10
  • header: void myFunction(struct MyStruct (& PassedStruct)[11])
  • caller: myFunction(StructArray)
  • status: does not compile, type of array mismatch between prototype and actual parameter
  • header: void myFunction(struct MyStruct PassedStruct[10])
  • caller: myFunction(StructArray)
  • status: works, PassedStruct is a pointer, size provided is ignored
  • header: void myFunction(struct MyStruct PassedStruct[11])
  • caller: myFunction(StructArray)
  • status: works, PassedStruct is a pointer, size provided is ignored
Morly answered 31/8, 2010 at 21:23 Comment(11)
@Philip Potter: yet another C++ zealot ? That's exactly why I put the word mostly in my answer. Trying to explain the difference between the two forms is a sure way to loose any C++ beginner (and only a beginner can ask such question). Please, go play with templates and let new people learn C++ ;-)Morly
downvote gone! the new edit is clearer though, shall we say, verbose? :)Ogham
I'm not C++ begginer and yet don't know the difference between type argument[] and type *argument; I've been looking for a while in C++ standard and googling around, can any of you point me to a place where I can learn the subtle differences in both notations? @PhilipPotter @MorlyCorinacorine
@Paula_plus_plus: in the context of parameter passing there is no difference. This is just two syntaxes for the same thing. Actually even if you put a size between the suqare brackets C++ will still understand the parameter as a pointer (and never copy arrays as people would expect for pass-by-value and the way it is done with say structures).Morly
If we put the size within the brackets, the type of the parameter will be int[size] not int * but the int[size] will decay to int * (f(int*) and f(int[size]) would be ambiguous). The array-to-pointer decay is well documented in C++ standard but I'm unable to find where the type[] semantics are documented. Do you know?Corinacorine
@paula-plus-plus: no, check the documentation/. even if we put the size inside the brackets (in function parameters, elsewhere it's not the same) the parameter is still a pointer. The C++ compiler just ignore the size. Just try it on a compiler, you'll see.Morly
#include <stdio.h> int fn(char x[1000]){ printf("%d\n", sizeof(x)); } int main() { char t[30] = {}; fn(t); }Morly
@paula-plus-plus: the way c++ handle the parameter declaration is not a decay. Decay is about the actual instance of pointers or arrays (cases where arrays are converted to pointers behind the scene), but this is not what happens here. Taking the sizeof of the parameter should make it obvious in my exemple.Morly
Thanks for the information @Morly I'm very interested in knowing where in the C++ standard all the type[] oddness is explained, I was unable to find out (maybe isn't stated on the standard?). Do you know where this is explained? (thanks)Corinacorine
The relevant line comes from function declaration details, there is a line stating: If the type is "array of T" or "array of unknown bound of T", it is replaced by the type Pointer to T (quoted from cppreference, not comiteed draft).Morly
@paula_plus_plus: the relevant quote from draft is here: eel.is/c++draft/dcl.fct#5Morly
O
9

Although arrays and pointers are conceptually different things, the waters are very much muddied by function parameters. You can't directly pass an array to a function; you can only pass a pointer. As a result, the language "helpfully" converts a prototype such as this:

void foo (char arr[])

into this:

void foo (char *arr)

And when you call the function, you don't pass it a complete array, you pass a pointer to the first element. As a result, inside the function foo, it will have a pointer to the original array, and assigning to elements of arr will change the array in the caller as well.

In all other situations outside of function parameters, array and pointer syntax in declarations are not equivalent, and refer to conceptually different things. But inside function parameter lists, array syntax creates pointer types.

Ogham answered 31/8, 2010 at 21:35 Comment(2)
I generally pass the pointer explicitly so it's clear what I had intended. It's just personal preference thoughAstounding
I tried to explain without any previous knowledge necessary (and mostly with a C point of view) the difference between pointers and array. It was fun to write, but I believe the result is gory.Morly
A
2

You could use a std::vector instead. Arrays are very much C constructs, C++ has wrapper classes to prevent exactly these kind of ambiguities

Alten answered 31/8, 2010 at 22:16 Comment(1)
you still have to use pointers to get a reference, and passing by value when copy constructors are present brings silent overhead if you don't realize it's happeningHammerskjold
H
1

I believe the x[] means the same thing as x* which means "pointer to the type". Since this is the case, you will be modifying the object you pass (you'll need to call it using the &, or 'address of' operator), and you can think of it as a reference.

Hammerskjold answered 31/8, 2010 at 21:23 Comment(0)
Y
1

When you pass an array as an argument to a function, the array decays to a pointer to the first element of the array.

So, when inside your function you use [] to access the elements of the array, you are really only doing pointer arithmetics with your initial pointer to get the elements of the ORIGINAL array.

So, yeah, you are modifying the original array. And this answer is pretty much independent of what compiler you are using (Although it's good practice, IMHO, to state the compiler in the question like you did)

Yesterday answered 31/8, 2010 at 21:29 Comment(0)
F
0

Yes, calling it by reference or by simple ways it will change the original data of the structure array. As we have also solve it on Visual Studio and the answer is in favour. Coode is given below:

#include <iostream>
using namespace std;
struct mystr {
    int a;
    int b;
    char c;
    int myarray[2];
};
void insert(mystr Passed[]) {
    Passed[0].a = 5;
    Passed[0].b = 6;
}

void modify(mystr PassedStruct[]) {
    PassedStruct[0].a = 1;
    PassedStruct[0].b = 2;
   
}
int main() {
    mystr StructArray[2];

    insert(StructArray);
    modify(StructArray);

    // Now StructArray has been modified by myFunction
  
    cout << StructArray[0].a << " " << StructArray[0].b << std::endl;

    return 0;
}

In the code first we given the array simple values then using modify function we modify it and after printing original answer was modified.

Flatware answered 17/12, 2023 at 15:22 Comment(1)
What is the added value of your answer compared to the others? Naming your function insert is somewhat misleading, as you are not inserting any value. The array has a fixed length.Dentoid

© 2022 - 2024 — McMap. All rights reserved.