2

I want to write a computer programme that will do the following things :


1a. It will make an array 3 characters long.

£££

2a. It will then initialize the array with the string "{_}" and will call the array zero.

0 = {_}

[The array is called 0 because it has 1 _ stick and because 2^0 = 1.]


1b. It will then ask the user to press Enter.

' '

2b. Once the user presses Enter it will lengthen the array such that its new length is one plus two times the previous length.

{_}££££

3b. It will then change the last '}' character into ',' .

{_,££££

4b. Make a side by side copy of the previous array.

{,{

5b. Put a '}' at the end of the array and call it one.

1 = {,{}}

[The array is called 1 because it has 2 _ sticks and because 2^1 = 2.]


It it then repeat the same process as in b so that the end result is -

5c.

2 = {,{},{,{}}}

[The array is called 2 because it has 4 _ sticks and because 2^2 = 4.]


So, this is my algorithm. I will produce the end results of a couple more iterations -

3 = {,{},{,{}},{,{},{,{}}}}

4 = {,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}}}

5= {,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}},{,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}}}}

And so on.



I wrote the following in order to get the aforementioned job done -

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

void print_vno ( int * pv_number , int * pv_length , char * pv_vno ) {
    printf ( "%d =\n" , *pv_number ) ;
    for ( int i = 0 ; i < *pv_length ; i++ )
    { printf ( "%c" , *( pv_vno + i ) ) ; }
    printf ( "\n" ) ;
}

void generate_vno ( int * gv_number , int * gv_length , char * gv_vno ) {
    *gv_number = *gv_number + 1 ;
    int old_length = *gv_length ;
    *gv_length = 2 * ( *gv_length ) + 1 ;
    gv_vno = realloc ( gv_vno , *gv_length ) ;

    for ( int i = 0 ; i < old_length ; i++ )
    { *( gv_vno + i + old_length ) = *( gv_vno + i ) ; }
    *( gv_vno + old_length - 1 ) = ',' ;
    *( gv_vno + *gv_length - 1 ) = '}' ;

    print_vno ( gv_number , gv_length , gv_vno ) ;
}

int main (  ) {
    int number = 0 , length = 3 ;
    char * vno = malloc ( length ) , zero [] = "{_}" ;

    for ( int i = 0 ; i < length ; i++ )
    { *( vno + i ) = zero [i] ; }
    print_vno ( &number , &length , vno ) ; 

    start :
    printf ( "\nPress Enter for successive Von Neumann Ordinals, " ) ;
    printf ( "or press X then Enter to exit.\n" ) ;
    char enter ;
    scanf ( "%c" , &enter ) ;
    if ( enter != 'x' )
    { generate_vno ( &number , &length , vno ) ;
    goto start ; }

    free ( vno ) ;
return 0 ; }


The problem I am having is as follows -

If I run the code here, it counts up to 10 then crashes and gives this error message- "** Process exited due to resource limitations **".

If I run the code here, it counts up to 4 then crashes and gives this error message- "free(): double free detected in tcache 2 Aborted".

I suspect there is something wrong with the usage of malloc, realloc and free.

My question is- how can I make it count up to 20?

Thank you.

11
  • 1
    gv_vno = realloc ( gv_vno... whatever you assign to gv_vno in that function, will not be visible to the caller after the function returns. Commented Jul 12 at 21:03
  • 3
    Don't use goto to construct loops. Do use a space before %c in the scanf() format string. Do check the return value from scanf() — if it doesn't return 1, you've got problems. Commented Jul 12 at 21:58
  • 4
    Unless you are getting paid by the number of lines you save, try opening your code up vertically by not trying to condense the scope of each block to a single line. It will make your code much easier to read, not only for you, but for others who review or maintain it, especially those with older eyes.... Yes, it's just a style thing, but it matters. Also, what happens if the user generates a manual EOF for scanf ( "%c" , &enter ) ; (e.g. tries to cancel with ctrl + d [or ctrl + z on windows])? Commented Jul 12 at 23:28
  • 3
    Your code layout is not idiomatic for C. You rarely need to create a loop with goto; the while and for (and dowhile) loops are usually adequate and orthodox indentation makes them easy to read. The leading blank in ” %c” means that white space such as the newline after the first input is ignored. That is normally what you want. Commented Jul 13 at 2:15
  • 5
    @uran42, do not post an answer in your question. Instead, roll back your question edit, post your answer below, just like any other answer. You can even accept it. Others can then vote up/down on it. See stackoverflow.com/help/self-answer Commented Jul 13 at 3:36

3 Answers 3

2
  • You realloc but you don't save the new pointer so that it's visible outside generate_vno. If realloc reallocates the data, the original pointer will have been free'd but you use the same pointer in all calls so you get undefined behavior. You need to send in a char** to generate_vno in order for the function to be able to store the new pointer.
  • You don't check the return value from scanf.
  • Use of pointers where pointers are not needed.
  • *(x + y) instead of the idiomatic x[y].
  • You use goto where a loop would work fine.
  • You instruct the user to enter X to quit, but only a lowercase x is actually working.
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// no pointers unless needed:
void print_vno(int pv_number, int pv_length, const char *pv_vno) {
    printf("%d =\n", pv_number);
    for (int i = 0; i < pv_length; i++) {
        putchar(pv_vno[i]);               // pv_vno[i] instead of *(pv_vno + i)
    }
    putchar('\n');
}

// `gv_vno` is now a `char**`:
void generate_vno(int *gv_number, int *gv_length, char **gv_vno) {
    *gv_number = *gv_number + 1;
    int old_length = *gv_length;
    *gv_length = 2 * (*gv_length) + 1;
    char *new_nvo = realloc(*gv_vno, *gv_length);
    if (new_nvo == NULL) { // check if realloc succeeded
        perror("malloc");
        free(*gv_vno);
        exit(1);
    }
    *gv_vno = new_nvo;     // save the new pointer

    for (int i = 0; i < old_length; i++) {
        new_nvo[i + old_length] = new_nvo[i];
    }
    new_nvo[old_length - 1] = ',';
    new_nvo[*gv_length - 1] = '}';

    print_vno(*gv_number, *gv_length, new_nvo);
}

int main() {
    int length = 3;
    int number = 0;
    char zero[] = "{_}";
    char *vno = malloc(length);
    if (vno == NULL) return 1;
    memcpy(vno, zero, length);
    print_vno(number, length, vno);

    for (;;) { // loop instead of a label and goto:
        printf("\nPress Enter for successive Von Neumann Ordinals, ");
        printf("or press X then Enter to exit.\n");
        char enter;
        // check that scanf succeeds and allow both `x` and `X` to quit:
        if (scanf("%c", &enter) != 1 || tolower((unsigned char)enter) == 'x')
            break;
        generate_vno(&number, &length, &vno);
    }
    free(vno);
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you. Much better. I also figured a work-around regarding the double pointer issue which I will post below. Can you say a bit more about this step "// check that scanf succeeds" and this step -"// check if realloc succeeded" please. I am very new to programming and haven't been taught these or never saw these. I will rectify the habit of using pointers whenever there is an array or a function. Thanks again.
@uran42 You're welcome! Glad it helped! scanf, malloc and realloc can all fail so you need to check the return values. scanf returns the number of successful extractions and since you want a single value, it should return 1, otherwise it has failed. malloc and realloc return NULL (nullptr) if they fail.
Thank you. I will research them in detail.
2

As noted, the memory pointer value from the reallocation does not, in effect, leave the called function, and so subsequent calls to the "generate_vno" work until a new chunk of memory has been used for the reallocation. To illustrate, I took your code and added in some "printf" statements to identify the memory address values within the function call and the value back in the "main" function.

craig@Vera:~/C_Programs/Console/Ordinal/bin/Release$ ./Ordinal 
0 = {_}

Press Enter for successive Von Neumann Ordinals, or press X then Enter to exit. g
Address of gv_vno before reallocation: 0x563678f272a0
Address of gv_vno after reallocation.: 0x563678f272a0
1 = {_,{_}}
Address of vno after function call...: 0x563678f272a0

Press Enter for successive Von Neumann Ordinals, or press X then Enter to exit. g
Address of gv_vno before reallocation: 0x563678f272a0
Address of gv_vno after reallocation.: 0x563678f272a0
2 = {_,{_},{_,{_}}}
Address of vno after function call...: 0x563678f272a0

Press Enter for successive Von Neumann Ordinals, or press X then Enter to exit. g
Address of gv_vno before reallocation: 0x563678f272a0
Address of gv_vno after reallocation.: 0x563678f27ae0    <----- Note the change to a new memory location
3 = {_,{_},{_,{_}},{_,{_},{_,{_}}}}
Address of vno after function call...: 0x563678f272a0    <----- However, the main function still refers to the original location

Press Enter for successive Von Neumann Ordinals, or press X then Enter to exit. g
Address of gv_vno before reallocation: 0x563678f272a0    <----- Thus when function called again, it attempts to free memory already freed
free(): double free detected in tcache 2
Aborted (core dumped)

As noted, after the third call, the memory location for the character array has changed, but this information does not get back to the "main" function. So then the subsequent fourth call to the "generate_vno" function fails upon the reallocation.

Noting that double pointer references are one solution, I will offer up another path to a solution of the memory update that has a bit of a "Python" flavor to it. Following is a refactored version of your program that returns the current memory value to the "main" function.

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

void print_vno (int * pv_number, int * pv_length, char * pv_vno)
{
    printf ("%d = ", *pv_number) ;

    for ( int i = 0 ; i < *pv_length ; i++ )
        printf ("%c", pv_vno[i]) ;

    printf ("\n") ;
}

char * generate_vno (int * gv_number, int * gv_length, char * gv_vno)
{
    int old_length = *gv_length ;
    char temp[old_length];                  /* Using a work array to assist in copying and expansion    */

    for (int i = 0; i < old_length; i++)
        temp[i] = gv_vno[i];

    *gv_number = *gv_number + 1;

    *gv_length = 2 * old_length + 1 ;

    free(gv_vno);                           /* Using free and malloc in lieu of realloc as a preference */
                                            /* FYI - may want to test for NULL value                    */

    gv_vno = malloc(*gv_length * sizeof(char));

    for (int i = 0; i < old_length; i++)
    {
        gv_vno[i] = temp[i];                /* Using usual and customary character indices              */
        gv_vno[i + old_length] = gv_vno[i];
    }

    gv_vno[old_length - 1] = ',';
    gv_vno[*gv_length - 1] = '}';

    print_vno ( gv_number, gv_length, gv_vno);

    return gv_vno;                          /* This gets the new memory location back to main            */
}

int main (void)
{
    int number = 0, length = 2 ;            /* To acquire the noted pattern wanted          */
    char * vno, zero [] = "{}", enter ;

    vno = malloc(length * sizeof(char));    /* Preference to highlight the allocation       */

    for (int i = 0 ; i < length ; i++)      /* Doing a character copy in lieu of a strcpy   */
        vno[i] = zero[i];

    print_vno (&number, &length, vno);

    while (1)                               /* Using "while" loop in lieu of "goto"         */
    {
        printf ("Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.\n");

        if (scanf (" %c", &enter) != 1 || (enter == 'x' || enter == 'X'))   /* Refactored key entry         */
            break;

        vno = generate_vno (&number, &length, vno);     /* Get updated memory location of character array   */
    }

    free (vno);

    return 0;
}

The major item to point out is refactoring the "generate_vno" function to return the memory location value so that it can be used to update the pointer value in the "main" function. This is not to discount the usage of double pointers, but just to illustrate an alternative method that might be clearer to anybody needing to analyze the program. Also, though using pointer arithmetic to copy and/or update character array values is absolutely valid, I again just offer up the usage of indices for positional reference within the array being expanded and populated that is often utilized. And, as had been noted in the good comments, one usually wants to avoid the usage of "goto" for loop processing. Therefore, the refactored code utilizes a "while" loop with a breakout when necessary.

With those refactoring tweaks and a slight tweak to the initial character array value to more closely replicate the desired Von Neumann pattern, following was a retest showing the terminal output which allowed to continual expansion of the character array without the program falling over.

craig@Vera:~/C_Programs/Console/Ordinal/bin/Release$ ./Ordinal 
0 = {}
Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.
g
1 = {,{}}
Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.
g
2 = {,{},{,{}}}
Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.
g
3 = {,{},{,{}},{,{},{,{}}}}
Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.
g
4 = {,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}}}
Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.
g
5 = {,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}},{,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}}}}
Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.
g
6 = {,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}},{,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}}},{,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}},{,{},{,{}},{,{},{,{}}},{,{},{,{}},{,{},{,{}}}}}}}
Press Enter for successive Von Neumann Ordinals, or press X and then Enter to exit.
x

Again, the main thrust of this answer is to provide some printed debug type output to illustrate what was happening in the original code and to provide another alternative in updating where in memory the character array was residing. As always, it is good to delve into "C" tutorials to get a better understanding of how items such as pointers work and the scope of information going to and coming from functions.

3 Comments

Why use a temporary storage and why not use realloc?
I utilized those bits of refactoring just to show some alternatives. Using "realloc" is absolutely good.
Thank you for your detailed answer. I started with some misconceptions about realloc I think, as soon as "double pointer" was mentioned I had a rough idea of what the misconception was. Your debugging completely eliminated those misconceptions I feel. We learned array-pointer-string together and most of the practice tasks were constrained with "only use typeless function", so that one is forced to use pointers. Using char * type function as you did eliminated the need of using double pointer I understand. Should have thought about this. Thanks again. Will upvote once I have enough reputation.
1

Upon receiving feedback from other users in the comment, I was able to make the code mentioned in the question work without any error. There are still some issues that need to be rectified such as using pointers where not needed, not checking return values of library functions and using if-goto loop (I have rectified this problem already). My solution goes as follows -

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

void print_vno ( int * pv_number , int * pv_length , char ** pvdp_vno ) {
    printf ( "%d =\n" , *pv_number ) ;
    for ( int i = 0 ; i < *pv_length ; i++ )
    { printf ( "%c" , *( *pvdp_vno + i ) ) ; }
    printf ( "\n" ) ;
}

void generate_vno ( int * gv_number , int * gv_length , char ** gvdp_vno ) {
    *gv_number = *gv_number + 1 ;
    int old_length = *gv_length ;
    *gv_length = 2 * ( *gv_length ) + 1 ;
    *gvdp_vno = realloc ( *gvdp_vno , *gv_length ) ;

    for ( int i = 0 ; i < old_length ; i++ )
    { *( *gvdp_vno + i + old_length ) = *( *gvdp_vno + i ) ; }
    *( *gvdp_vno + old_length - 1 ) = ',' ;
    *( *gvdp_vno + *gv_length - 1 ) = '}' ;

    print_vno ( gv_number , gv_length , gvdp_vno ) ;
}

int main (  ) {
    int number = 0 , length = 3 ;
    char * vno = malloc ( length ) , zero [] = "{_}" ;
    char * dp_vno [] = { vno } , enter ;

    for ( int i = 0 ; i < length ; i++ )
    { *( vno + i ) = zero [i] ; }
    print_vno ( &number , &length , dp_vno ) ;

    do {
    printf ( "\nPress Enter for successive Von Neumann Ordinals, " ) ;
    printf ( "or press X then Enter to exit.\n" ) ;
    scanf ( "%c" , &enter ) ;
    if ( enter != 'x' )
    { generate_vno ( &number , &length , dp_vno ) ; }
    } while ( enter != 'x' ) ;

    free ( *dp_vno ) ;
return 0 ; } 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.