0

I was wondering if it was okay to pass around/return structs holding statically allocated arrays in C++. For example:

typedef struct t{
    char name[32];
    int id;
}t;

t foo(char name[]){
    t my_t = {name, 0};
    return my_t;
}

int main(){
    t main_t = foo("a struct");
    printf("%s", main_t.name); //is this safe to access? 
    return 0;
}

When it returns the struct by value, I guess it doesn't copy the array name, only the pointer to it.

Edit: Just to clear up some things. I do know that char *var is equal to char var[] in function arguments. I wrote this code up really quickly without even testing it. I know it is definitely not the best code in the world and I wouldn't advice anybody to use it in real life. Ideally I would allocate the struct dynamically and pass around a pointer, however, I am teaching a person to program in C/C++. It is for a national exam which doesn't require a person to know dynamic allocation or pointers for that matter, that is why * are not really used at all.

The question is really about whether it is okay to return a struct holding a statically allocated array.

Shall we use this piece of code instead:

#include <iostream>

struct t{
    char name[32];
    int id;
};

t foo(int id){
    t my_t;
    my_t.id = id;
    for(char i = 0; i < 31; i++){
        my_t.name[i] = 'a';
    }
    my_t.name[31] = '\0';
    return my_t;
}

int main(){
    t main_t = foo(0);
    std::cout << main_t.name; //is this safe to access? 
    return 0;
}
  • 2
    This shouldn't compile since you're trying to assign a `char*` to a `char[]` here `t my_t = {name, 0}`. – legends2k May 02 '14 at 11:20
  • 1
    Mixing up C and C++ is not a good idea in most cases. For example, a C++ answer may define copy constructor and copy assignment, whereas a C answer may introduce a clone method. – Sebastian Mach May 02 '14 at 11:22
  • 2
    `foo(char name[])` is useless. Basically it is a `foo(char*)` without any size –  May 02 '14 at 11:22
  • 1
    @MrSpreadsheet Which language c or c++? – this May 02 '14 at 11:28

4 Answers4

2

It can be done.

But it shouldn't be done: alternatives are std::string and std::array. You get your copy, allocation, deallocation etc... for free!

struct t {
    std::string name;
    int id;
    std::array<int, 10> integers;
};

...

t main_t = {"a struct"};
main_t.integers[5] = 5;
t copy = main_t;
assert( copy.name == "a struct" );
assert( copy.id == 0 );
assert( copy.integers[5] == 5 );
xtofl
  • 40,723
  • 12
  • 105
  • 192
1
  1. There is no static array in your code.

  2. When you return a t instance by value, it will copy the contents of the array.

  3. The problem is with how you're initializing the t instance, not with how you're returning it.


Change this:

t my_t = {name,0};

To this:

t my_t = {0};
strncpy(my_t.name,name,sizeof(my_t.name)-1);

Or to this, if you want to avoid the use of library functions:

int i;
t my_t;
for (i=0; i<sizeof(my_t.name)-1 && name[i]!=0; i++)
    my_t.name[i] = name[i];
my_t.name[i] = 0;
my_t.id = 0;
barak manos
  • 29,648
  • 10
  • 62
  • 114
  • [Better use `strncat`](http://stackoverflow.com/questions/1453876/why-does-strncpy-not-null-terminate) – leemes May 02 '14 at 11:36
  • Click on the link ;) If the source string is longer than the destination can hold, it will not be null terminated. `strncpy` doesn't copy a string, since the result is not necessarily a "string" (null terminated). – leemes May 02 '14 at 11:37
  • 1
    @leemes: That's what the `t my_t = {0}` is for. – barak manos May 02 '14 at 11:38
  • Now that you have `sizeof(...)-1` it seems to be safe, but I remember you didn't have that before. – leemes May 02 '14 at 11:39
  • @leemes: Yeah, I added it immediately after committing the initial answer. Thanks. – barak manos May 02 '14 at 11:43
0

You've an error in your program; you're trying to copy a pointer to an array, instead you should copy the contents pointed to by the pointer into the array. It should be like this

#include <algorithm>
#include <cstring>

struct t
{
    char name[32];
    int id;
};

t foo(const char *name)
{
    t my_t = {};
    const size_t len = std::strlen(name);
    const size_t max_len = sizeof(t::name) / sizeof(t::name[0]) - 1u;
    std::copy(name, name + std::min(len, max_len), my_t.name);
    return my_t;
}

int main()
{
    t main_t = foo("a struct");
    printf("%s", main_t.name);
}

As for your question

I was wondering if it was okay to pass around/return structs holding static arrays in C++

Yes, it's ok, the whole struct will get copied (or moved, depending on the type) to the variable at the calling end.

legends2k
  • 31,634
  • 25
  • 118
  • 222
0

It's OK to pass around structs that contain arrays. However, this line doesn't do what you think it does:

t my_t = {name, 0};

Using designated initializer syntax, it would be equivalent to:

t my_t = {  .name[0] = name;  .name[1] = 0; };

The first member of t is an array with 32 elements, so the first 32 initializers are applied to those 32 chars. Unless you use some more braces, but you still need one initializer per element, there's no magic way to get a strcpy out of brace-enclosed initializers.

You should get a compiler error about initializing a char with a pointer.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • So basically, when I define the size of an array in a struct at the compile time, does the memory get allocated inside the struct itself, instead of a pointer? And then when I return it, the whole of the array gets copied? That was the question which was bugging me. I mean, struct access works with offsets, so it would be logical. – MrSpreadsheet May 02 '14 at 12:18