Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C program: Use an array outside the scope it was defined in

I have some code in which an array of strings is defined in a conditional statement. The array is out of scope where I need it. So I defined another pointer in the outer scope. In the conditional statement I assign the array address to the outer scope pointer. The code compiles without warnings for dangling pointers and such, but the code is not correct.

I produced a (correctly compiling) MRE below. The function does not make any sense. Essential is that:

  • The array of strings cmd_str is defined inside a condition
  • The number of strings is variable
  • The length of the strings is variable
  • The strings are either constants or null terminated strings
  • The last string only contains NULL
  • I use the resulting strings array outside the condition
int noCmd(int *opt) {

    int             local_opt;
    const char**    cmd = NULL;

        local_opt = *opt; // Make sure the compiler cannot know this value to avoid optimization.
        if (local_opt == 1) {
            const char* cmd_str[] = {"foo", "bar", NULL};
            cmd = cmd_str;
        } else {
            const char* cmd_str[] = {"flop", "blah", NULL};
            cmd = cmd_str;
        }
    // Dummy statement to force referencing cmd
    // actually I need the contents of cmd here
    return sizeof(cmd);
}

The main problem is that cmd doesn’t have any value in the return statement. This is confirmed by the assembly listing where cmd does not get a value assigned:

    Dump of assembler code for function noCmd:
    0x00007ffff7fb9e80 <+0>:     mov    $0x8,%eax
    0x00007ffff7fb9e85 <+5>:     ret
    End of assembler dump.

My reasoning is that cmd_str goes out of scope. Since it is not a valid pointer anymore, it is not allowed to be used. And the compiler optimizes all code out, because the nothing is used inside the scope and must not be used outside the scope.

The actual code is obviously much more complicated that this MRE. But the problem (conditional char** assignment) and the need for the strings outside the scope is the same.

How do I solve this?

I cannot do a memcpy from cmd_str into cmd, because I don't know the size. I might loop through each string in cmd_str, find the size of the string and then copy each string into cmd one by one, but that is very inelegant.

I cannot declare cmd_str in the outer scope, because

const char* cmd_str[]

is invalid.

Neither can I declare

const char** cmd_str

because it would be impossible to assign a number of strings to cmd_str[].

Isn't there a better way to define an array of strings inside a condition and use it outside the condition? The string to be assigned are either constants or null terminated strings. The last string gets the NULL value.

like image 384
Johannes Linkels Avatar asked Jul 08 '26 17:07

Johannes Linkels


1 Answers

I have some code in which an array of strings is defined in a conditional statement. The array is out of scope where I need it. So I defined another pointer in the outer scope. In the conditional statement I assign the array address to the outer scope pointer. The code compiles without warnings for dangling pointers and such, but the code is not correct.

Code such as you describe is not incorrect, but it becomes incorrect (in the sense of having undefined behavior) as soon as you dereference that outer pointer, or use its value in any other way before assigning a new one. This is because the lifetime of the array declared inside the conditional ends as soon as execution of the innermost containing block does, where "block" in this case means any containing statement, not just one delimited by braces. In your case, it is whichever clause of the conditional is executed.

Essential is that:

  • The array of strings (cmd_str) is defined inside a condition

[...]

  • I use the resulting strings array outside the condition

If that's truly essential then you are out of luck. If you define an array anywhere inside a conditional statement, then its lifetime is over by the time execution of the conditional terminates. Outside the conditional, there is no longer any array to access. Assigning a pointer to point to it while it is still alive doesn't change that.

How do I solve this.

One way would be to dynamically allocate memory inside the conditional, once you know how much you need. You then assign or copy whatever value you need into the dynamically allocated space, and set the pointer to point to it. The dynamically allocated object lives until it is explicitly deallocated (so don't forget to deallocate it when you no longer need it).

Example:

    const char **cmd;

    if (local_opt == 1) {
        const char* cmd_str[] = {"foo", "bar", NULL};
        cmd = malloc(sizeof cmd_str);
        // ... abort if null ...

        memcpy(cmd, cmd_str, sizeof cmd_str);
    } else {
        // ...
    }

There are other ways to work the details.

Note well that the usage of string literals in the above is not problematic, because these represent objects having static storage duration. Their lifetime is the entire execution of the program. It's unclear what you mean, however, when you say the array elements may include (pointers to) null-terminated strings. The string literals satisfy that description, but if you mean anything else by that then there are lifetime considerations surrounding those, too.

If you don't need to be able to modify the array once produced, then another alternative that would be suitable at least for the example given would be to declare the inner arrays static, so that their lifetimes are not bounded by the execution of the conditional:

    const char * const *cmd;

    if (local_opt == 1) {
        static const char * const cmd_str[] = {"foo", "bar", NULL};
        cmd = cmd_str;
    } else {
        // ...
    }

The additional const qualification is not essential, but it helps protect against the array being modified. This is important because any changes will persist indefinitely, through the end of the program's execution, across any number of calls to the function containing that code.

like image 108
John Bollinger Avatar answered Jul 11 '26 12:07

John Bollinger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!