2
votes

I have a struct that contains a value of type char*[]. It's defined like so:

struct background_element
{
    pid_t pid;
    int number;
    char *full_command[MAX_ARGS];
};

I also have a global char*[] variable, args.

char *args[MAX_ARGS];

I try to create a new variable of type struct background_element and assign the full_command value like so:

struct background_element bg_elem = { .pid = child, .number = num_background, .full_command = args};

However, the assignment of args to .full_command seems to throw this warning: "Warning: initialization from incompatible pointer type."

I've tried to use strcpy, but since they're not char[] but char*[], it doesn't seem to work. I'm a bit at a loss of how to assign this. Any help would be appreciated.

4
Arrays cannot be assigned, only copied. So you need to write a for loop that copies each of the pointers in args into the corresponding location in full_command. - user3386109

4 Answers

2
votes

As @user3386109 said, it's because char *full[MAX_ARGS] cannot be assigned to. There's two paths from here depending on your scenario. If args isn't going to be modified, either by other code or by freeing its string elements, then we can just point the elements of full_command to them like so:

struct background_element bg_elem = {.pid = child, .number = num_background};
for (unsigned int i = 0; i < MAX_ARGS; i++) {
    bg_elem.full_command[i] = args[i];
}

If however you need to copy the strings, then you'll have to use malloc to create space for the strings, and then strcpy to copy the data over:

for (unsigned int i = 0; i < MAX_ARGS; i++) {
    int str_len = strlen(args[i]);
    bg_elem.full_command[i] = malloc((str_len + 1) * sizeof(char));
    strcpy(bg_elem.full_command[i], args[i]);
}

The above for loop first:

  1. Get the length of the ith arg
  2. Allocate enough memory to the ith pointer of bg_elem.full_command
    • We need str_len + 1 because we need space for the null character
  3. Copy the string from args to bg_elem.full_command

Finally, if you're needing to copy the strings from args don't forget to iterate through and free that memory at the end when you're done.

for (unsigned int i = 0; i < MAX_ARGS; i++) {
    free(bg_elem.full_command[i]);
}

Note: If you need the memory that you are copying to be zeroed, calloc would be better. However since we're using strcpy, malloc works just fine.

1
votes

Array objects in C are limited to only two kinds of initializers:

  1. {}-enclosed initializers, i.e a pair of {} with individual initializers for array elements inside
  2. String literals for char [] arrays.

Your initializer does not fall into any of these categories. It is invalid. You cannot initialize one array by another array (aside from case 2 above).

Formally, you can spell it out explicitly

struct background_element bg_elem = 
{ 
  .pid = child, 
  .number = num_background, 
  .full_command = { args[0], args[1], /* ... and so on */ }
};

but this is not really a viable approach. A much better idea would be

struct background_element bg_elem = 
{ 
  .pid = child, 
  .number = num_background
};

static_assert(sizeof bg_elem.full_command == sizeof args, "");
memcpy(bg_elem.full_command, args, sizeof args);

although it might suffer a bit from "double initialization" issue.

P.S. What you are trying to do is called initialization, not assignment. Assignment is a very different thing in C.

0
votes
char *args[MAX_ARGS];

args is an array of MAX_ARGS pointers. args itself points to the memory of the first pointer. args hold the memory address in which the first pointer is. args[0] is the value of the first pointer. args[0][0] means, that we go to the memory of the first pointer, then go to the memory address pointed to by that pointer, and then get the value of the first byte in that memory address.

char *full_command[MAX_ARGS];

Now, this is also an array of MAX_ARGS pointers. full_command points to memory region that is MAX_ARGS * sizeof(char*) bytes length. full_command[0] is the value of the first pointer inside that memory region.
Now lets try assignment:

full_command = args;

Now we get the value of the memory address of the first pointer in args and assign that value into full_command variable. The memory of full_command is lost, it's no longer accessible by any other handle. Now args[0] = smth only and only if full_command[0] = smth. full_command points to the memory region that args points to.
To copy array VALUES you need to copy each VALUE of the array:

for (size_t i = 0; i < MAX_ARGS; ++i) { 
     full_command[i] = args[i];
}

or using memcpy:

memcpy(full_command, args, sizeof(full_command));

After such operation full_command points to a different region that args, both are sizeof(char*) * MAX_ARGS bytes long. And they both hold the same values.
You need to assign each array value:

struct background_element bg_elem = {
   .pid = child, 
   .number = num_background, 
   .full_command = { args[0], args[1], args[2], ....<up until MAX_ARGS> }, 
};

That's not very usuable, it needs to be modified each time MAX_ARGS changes. So use memcpy or a loop:

struct background_element bg_elem = {
    .pid = child, 
    .number = num_background, 
};
memcpy(bg_elem.full_command, args, sizeof(bg_elem.full_command));
0
votes

You have a lot of correct answers above. Simply put: arrays are weird in C. You can initialize them, but not assign them. You can't individually initialize them in a struct initializer, but you can assign them as part of a struct assignment.

#include <string.h>

int main(int argc, char *argv[])
{
  char *test1 [3] = {"a", "b", "c"}; //initialization, not assignment; allowed
  char *test2 [3];

  //test2 = test1; //assignment of arrays; not allowed

  memcpy(test2, test1, sizeof(test1)); //the right way to do a shallow array copy
}

But also, confusingly:

#include <string.h>

struct _array_holder
{
  char *array[3];
};

int main(int argc, char *argv[])
{
  struct _array_holder test1 = {{"a", "b", "c"}}; //_struct_ initialization; allowed
  struct _array_holder test2;

  test2 = test1; //assignment of _structs_; allowed!

  memcpy(&test2, &test1, sizeof(test1)); //also a fine way to copy a struct
}

So, unfortunately, there is no pretty way (i.e., elegant syntax) to do what you want. Here's at least a reasonably self-contained way to use the fact that array assignments within structs work, to keep you from having to write several lines of initialization each time you want to populate a new struct:

#include <string.h>
#include <unistd.h>

#define MAX_ARGS 5
struct background_element
{
  pid_t pid;
  int number;
  char *full_command[MAX_ARGS];
};

//something akin to a constructor for the struct
struct background_element init_background_element(pid_t pid, int number, char *full_command[])
{
  struct background_element ret = {.pid = pid, .number=number};
  memcpy(ret.full_command, full_command, sizeof(ret.full_command));
  return ret;
}

int main(int argc, char *argv[])
{
  pid_t child = 1;
  int num_background = 5;
  char *args[MAX_ARGS] = {"a", "b"};

  //use of the constructor-like thing
  struct background_element bg_elem = init_background_element(child, num_background, args);

  return bg_elem.pid;
}