0

This is what I expect my string array s to be after the program is run: {"#0", "#1", "2"}.
This is what I am getting: {"#2", "#2", "2"}.
How do I modify this code so that I can get {"#0", "#1", "#2"} in the main after the function is executed? What am I doing wrong? Please help.

#include <stdio.h>

void func(char **s){
    for(int i=0; i<3; i++){
        char buf[10];
        snprintf(buf, 10, "#%d",i);
        s[i]=buf;
    }
}
int main()
{
    char *s[3];
    func(s);
    for(int i=0; i<3; i++){
        printf("%s", s[i]);
    }
    return 0;
}
Unyaya
  • 68
  • 7
  • 2
    Please learn to code in pure C++ no in `C` like code. Your code has Undefined behavior since returns a pointer to local variable, which lifetime ends. – Marek R Jan 10 '23 at 16:30
  • 2
    Your bug is `s[i]=buf;` remember that buf is a variable that is local to the scope inside the for loop. Once you hit the `}` the buf variable and it's contents no longer exist but you are copying pointers not what the pointers point to so you save pointers to non-existant objects. – drescherjm Jan 10 '23 at 16:30
  • 1
    There are several different ways to do this and it's not possible to tell you exactly how, without knowing more about your homework assignment or class assignment. You are obviously expected to do this in a specific way, whether via dynamic allocation, preallocated buffers, or something else. What is the topic of the chapter in your C++ textbook, or your C++ class where this practice problem was assigned? It goes without saying that if you don't do this in the expected way, you won't get credit for your solution. – Sam Varshavchik Jan 10 '23 at 16:30
  • I can't use string because am trying to learn char** @RichardCritten – Unyaya Jan 10 '23 at 16:32
  • 3
    Then you are learning `C` and not `C++`. Manual memory management should be an advanced topic and require justification to be used. – Richard Critten Jan 10 '23 at 16:33
  • https://godbolt.org/z/o3x8fen4c – Marek R Jan 10 '23 at 16:36
  • I am very lost. Could you please tell me how can I make it so that the *s has the different strings like intended then? @drescherjm – Unyaya Jan 10 '23 at 16:38
  • 1
    @Unyaya `{ char buf[10]; ... }` - at the `}` you loose `buf`. It is no more. Anything you made point at `buf` is now a "dangling" pointer and reading from memory where those dangling pointers point has _undefined behavior_. – Ted Lyngmo Jan 10 '23 at 16:40
  • Nobody will be able to tell you how to "make it so", until you explain exactly what C++ topic you are expected to learn from this programming task. "`*s has the different things`" is too vague and generic, and it's like someone who's learning how to fix cars describing his task as "making the car go forward and making "vroom!" noises", instead of explaining that they're trying to learn how to fix the engine, the brakes, the exhaust system, etc. What C++ topic, ***specifically*** are you expected to learn from this? – Sam Varshavchik Jan 10 '23 at 16:41
  • @SamVarshavchik I don't know how to answer that question because this is not for a class. I am not required to do anything. I am just trying to learn how to modify double char pointers in another function in C on my own. – Unyaya Jan 10 '23 at 16:43
  • @RichardCritten You are right. The program is in C and I have edited the question now. If you could take a look and help out, I would be very grateful – Unyaya Jan 10 '23 at 16:44
  • 1
    Here's the right way to learn C: get a C textbook; open to Chapter 1; read a chapter a time, as each programming topic is explained, in logical order, from easiest to the hardest, with the appropriate programming tasks in each chapter to work on. Here's the wrong way to learn C: skim a few blogs, or watch random Youtube videos, then attempt to cobble together C program that do random things, out of thin air, hoping to learn something. Nobody will be able to effectively learn C, or any other programming language, with this kind of an ad-hoc, unorganized approach. – Sam Varshavchik Jan 10 '23 at 16:45
  • @SamVarshavchik I did read up on double pointers and references but the examples in my book are all int. That's why I am trying to learn with char**. I didn't watch random videos or read random blogs – Unyaya Jan 10 '23 at 16:48
  • So, why don't you try to do the specific examples in your book, then? – Sam Varshavchik Jan 10 '23 at 16:50
  • `I am trying to learn with char**` first learn `char*` it's not that much different. You would have the same issue if you wrote `char* test; {char buf[]("Testing"); test = buf}` – drescherjm Jan 10 '23 at 16:59
  • You need to make a copy of buf's contents on each iteration of the loop and not try to copy a pointer. – drescherjm Jan 10 '23 at 17:00
  • @Unyaya `s[i]=something;` is the correct way to "modify a char** in another function". Yet that _something_ needs to be valid outside `for` loop. Research `strdup()`. – chux - Reinstate Monica Jan 10 '23 at 17:13

5 Answers5

3

First, you have every element in your array pointing to the same character array.

Second, they are all pointing to a local character array. This leads to undefined behavior.

Local variables in functions get allocated on the stack. When the function call is over, that memory may be overwritten with some other value.

Chris
  • 26,361
  • 5
  • 21
  • 42
  • Do you know how I can change that so that I can use the local variable to change my pointer? – Unyaya Jan 10 '23 at 16:46
  • 3
    You can't. C does not work like this. Local variables that get declared in a function get destroyed when execution leaves the scope, leaving you with a pointer to a destroyed object. That's why you need to learn C using a good textbook that explains these fundamental C concepts, instead of attempting to write some ad-hoc code. You should stick to the lesson plan in the C textbook, for best results. – Sam Varshavchik Jan 10 '23 at 16:51
  • @SamVarshavchik I understand that each of the *s points to the same local variable which is why I am getting "#2" for every element because that was the value of the local variable at the end of the loop. Isn't it possible to dereference my pointer such that the value of the pointer itself changes? If I wrote s[0]="ABC" for instance the value of the pointer itself would change. – Unyaya Jan 10 '23 at 16:57
  • 2
    @Unyaya You could create a copy of your local buffer in dynamically allocated memory for every pointer to be returned. Read about `strlen`, `malloc` and `strcpy` or, if available, `strdup`. Note that the copy of a string needs one byte more than the number returned from `strlen`. – Bodo Jan 10 '23 at 17:01
  • @SamVarshavchik I guess the specific topic would be char double pointers and how to modify them in another function. Does that help? – Unyaya Jan 10 '23 at 17:07
  • It does not have to be char double pointers. You would have the same problem with `char*` here: [https://ideone.com/k3EIQP](https://ideone.com/k3EIQP) – drescherjm Jan 10 '23 at 17:09
  • There's nothing unique about "char double pointers", and they are modified and used in functions the same way that any other object, or a pointer to the object is used. The issue is not about "char double pointers" specifically, but fundamental rules in C, and C++, regarding scoping, and when objects get created and destroyed. This would be one of core C and C++ fundamental concepts, that should be fully explained in every C and C++ textbook. Stackoverflow is not a textbook replacement. See your textbook for a complete discussion of this topic, and if something's unclear, feel free to ask. – Sam Varshavchik Jan 10 '23 at 18:53
3

Given two pointers p and q, the statement:

p = q;

doesn't copy the contents of the memory pointed to by q to the contents of the memory pointed to by p. It copies the pointer values, such that both p and q now point to the same memory, and any change to the memory via p is reflected when q is used.

That being said, the statement:

char buf[10];

declares buf to be an array of 10 chars. It has a lifetime corresponding to the execution of its block of definition. Once the function returns, it's destroyed and s is now indeterminate. Indeterminate pointers lead to undefined behaviour.

Possible Solutions:

  • standard strcpy
  • POSIX's strdup (which will be included in C23)

Note that the strdup() function returns a pointer to a new string which is a duplicate of the provided string. Memory for the new string is obtained with malloc, and must be freed by the calling process with free.

Harith
  • 4,663
  • 1
  • 5
  • 20
  • do you know how i can copy the contents of a pointer and add it to my original one like I intend? – Unyaya Jan 10 '23 at 17:10
  • 1
    You can allocate a new buffer with `s[i] = new[strlen(buf)]` and use strcpy or use strdup. For strdup() you can do `s[i] = strdup(buf);` since strdup() allocates a buffer for you using malloc: [https://en.cppreference.com/w/c/experimental/dynamic/strdup](https://en.cppreference.com/w/c/experimental/dynamic/strdup) – drescherjm Jan 10 '23 at 17:13
  • When I do *s[i]=*buf, my printf outputs nothing. – Unyaya Jan 10 '23 at 17:13
  • What does s[i] point to? The answer is nothing. You need to allocate s[i] first before the copy. – drescherjm Jan 10 '23 at 17:14
  • @Unyaya Since it has been changed into a C question, take a look at `malloc`/`free` for allocating/releasing memory dynamically – Ted Lyngmo Jan 10 '23 at 17:15
1

@Chris’s answer tells you what is wrong.

To fix it, you have options. The simplest is to make the argument array have strings (char arrays) that are big enough for your uses:

#define MAX_STR_LEN (9+1)  // Every string’s capacity is 9+1 characters

void func(size_t n, char array_of_string[][MAX_STR_LEN])
{
    for (size_t i=0;  i<n;  i++)
    {
        snprintf(array_of_string[i], MAX_STR_LEN, "#%d", (int)i);  // use the extant string
    }
}

int main(void)
{
    char array_of_string[3][MAX_STR_LEN] = {{0}};  // Array of 3 strings
    func(3, array_of_string);
    ...
    return 0;
}

If you want to play with dynamic char * strings, life gets only a little more complicated:

void func(size_t n, char *array_of_string[])
{
    for (size_t i=0;  i<n;  i++)
    {
        free(array_of_string[i]);              // free any pre-existing string
        array_of_string[i] = calloc( 10, 1 );  // allocate our new string
        if (!array_of_string[i]) fooey();      // always check for failure
        snprintf(array_of_string[i], 10, "#%d", (int)i);  // use the new string
    }
}

int main(void)
{
    char *array_of_string[3] = {NULL};  // Array of 3 dynamic strings, all initially NULL
    func(3, array_of_string);

    ...
    
    for (size_t i=0; i<3; i++)          // Don’t forget to clean up after yourself
        free(array_of_string[i]);
    return 0;
}

Ultimately the trick is to manage the size of your strings, remembering that a string is itself just an array of char. You must ensure that there is enough room in your character array to store all the characters you wish. (Good job on using snprintf()!


Also remember that in C any argument of the form array[] is the same as *array. So our functions could have been written:

void func(size_t n, char (*array_of_string)[MAX_STR_LEN])

or

void func(size_t n, char **array_of_string)

respectively. The first is an uglier (harder to read) syntax. The second is nicer, methinks, but YRMV.

Finally, if you are using C99 (or later) you can tell the compiler that those arguments are, actually, arrays:

void func(size_t n, char array_of_string[n][MAX_STR_LEN])

or

void func(size_t n, char *array_of_string[n])

MSVC does not support that syntax, though, and probably never will, alas.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
  • 1
    Thank you so much for your answer and your supportive/encouraging tone. It gets overwhelming sometimes so I really really appreciate it! – Unyaya Jan 10 '23 at 17:41
1
{ // start of a new scope
    char buf[10]; // a variable with automatic storage duration
    // ...
} // end of scope - all automatic variables end their life

In your code, you make pointers point at buf which has seized to exist (3 times) at the } in the for loop. Dereferencing (reading from the memory those pointers point at) those pointers afterwards makes your program have undefined behavior (anything could happen).

  • What you can do is to allocate and release memory dynamically using malloc and free.

When sending in a pointer to the first element in an array of elements to a function like you do, it's also customary to provide the length of the array (the number of elements in the array) to the function.

It could look like this:

#include <stdio.h>
#include <stdlib.h>

// A macro to calculate the number of elements in an array
// sizeof (x)   - the number of bytes the whole array occupies
// sizeof *(x)  - the size of the first element in the array
//                (all elements have equal size)
// The result of the division is the number of elements in the array.
#define SIZE(x) (sizeof (x) / sizeof *(x))

void func(char *s[], size_t len) {
    for (size_t i = 0; i < len; i++) {
        // calculate the required length for this string
        size_t req = snprintf(NULL, 0, "#%zu", i) + 1; // +1 for '\0'
        // and allocate memory for it
        s[i] = malloc(req);
        if(s[i] == NULL) exit(1); // failed to allocate memory
        snprintf(s[i], req, "#%zu", i);
    } // dynamically allocated memory is _not_ released at the end of the scope
}

int main() {
    char *s[3];
    func(s, SIZE(s));
    for (size_t i = 0; i < SIZE(s); i++) {
        puts(s[i]);
        free(s[i]); // free what you've malloc'ed when you are done with it
    }
}

Note that with the use of the macro there is only one hardcoded 3 in the program. Even that could be made into a named constant (#define CHAR_PTRS (3) or enum { CHAR_PTRS = 3 };) to further the ease of reading and maintaining the code.


A non-idiomatic version only accepting a pointer to an array of a fixed (at compile time) size could look like like below. In this example you couldn't accidentally provide a pointer to an array with only 2 char* (which would cause the function to write out of bounds). Instead, it'd result in a compilation error.

#include <stdio.h>
#include <stdlib.h>

// Here `s` is a pointer to an array of 3 char*
void func(char *(*s)[3]) { 
    for (int i = 0; i < 3; i++) {
        (*s)[i] = malloc(10);
        if((*s)[i] == NULL) exit(1);
        snprintf((*s)[i], 10, "#%d", i);
    }
}

int main() {
    char *s[3];
    func(&s);                      // &s is a char*(*)[3]
    for (int i = 0; i < 3; i++) {
        printf("%s\n", s[i]);
        free(s[i]);
    }
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
0
#include <stdio.h>
#include <string.h>

void func(char **s){
    for(int i=0; i<3; i++){
        s[i]=malloc(sizeof(char) * 100);
        char buf[10];
        snprintf(buf, 10, "#%d",i);
        strcpy(s[i], buf);
    }
}
int main()
{
    char *s[3];
    func(s);
    for(int i=0; i<3; i++){
        printf("%s", s[i]);
    }
    return 0;
}

This fixed my problem. My understanding is that I assigned memory and then copied the contents of buf to s to the now-present memory.

Unyaya
  • 68
  • 7
  • 1
    `s[i]=malloc(sizeof(char) * 100);` you don't need or want `100` as that could cause future bugs if the size of the string was ever larger than 100. You can use strlen(buf) after you filled the buf. – drescherjm Jan 10 '23 at 17:26
  • 1
    `malloc` is used, so where is `free`? – Marek R Jan 10 '23 at 18:15