C program Changing argv programmatically gives heap corruption error after exit of program only in debug mode in Visual Studio. How to resolve?

I have a large legacy program in which programmatically argv parameter is changed programmatically in program init followed by logic that parses the parameters.

In Release mode the program terminates gracefully.

In Debug mode, the program does all the required computation and gives the correct output. But upon exit gives Heap corruption error:

enter image description here

Error Message:


Microsoft Visual C++ Runtime Library

Debug Error!

Program: ...sers\AMD\source\repos\ArgvOverflow\x64\Debug\ArgvOverflow.exe

HEAP CORRUPTION DETECTED: after CRT block (#62) at 0x00000259566FDC90. CRT detected that the application wrote to memory after end of heap buffer.

Memory allocated at minkernel\crts\ucrt\src\appcrt\startup\argv_parsing.cpp(285).

(Press Retry to debug the application)


Abort Retry Ignore


Code:

#include <stdio.h>
int main(int argc, char **argv)
{
       argc = 12;
       argv[1] = "str1";
       argv[2] = "str2";
       argv[3] = "str3";
       argv[4] = "str4";
       argv[5] = "str5";
       argv[6] = "str6";
       argv[7] = "str7";
       argv[8] = "str8";
       argv[9] = "str9";
       argv[10] = "str10";
       argv[11] = "str11";
       printf("Hello world\n");
       return 0;
}

I have read several posts about modifying argv, where they claim that such modifications are legal according to C standards. I also tried the suggestion to have the line

argv[argc] = NULL;

This is not solving the problem.

Upvotes: 0

Views: 454

Answers (5)

It is legal to modify the characters of the argument strings. C11 5.1.2.2.1p2

The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination.

It is still not allowed to access the array out of bounds, argv will have only argc + 1 elements (with the value of argc as in the beginning of the main), not as many as you try to stuff in there.

Upvotes: 2

Ahmed
Ahmed

Reputation: 73

It is totally plausible to have this kind of setup when doing debug. One work around which worked for me was to set up a dummy argument list when calling the program, then repopulate argv with the desired data once you get into main.

myprog.exe dummy dummy dummy dummy dummy dummy dummy dummydummy dummydummy dummy 

and inside your code

    int main (int argc , char* argv[]){

       argc = 12;
       argv[1] = "str1";
       argv[2] = "str2";
       argv[3] = "str3";
       argv[4] = "str4";
       argv[5] = "str5";
       argv[6] = "str6";
       argv[7] = "str7";
       argv[8] = "str8";
       argv[9] = "str9";
       argv[10] = "str10";
       argv[11] = "str11";
       printf("Hello world\n");
       return 0;
}

Upvotes: 0

Lundin
Lundin

Reputation: 213136

You are allowed to modify argc and argv, but that doesn't mean that C suddenly handles (re)allocation of these variables for you. argv will be an array of type char* argv[argc];. It will contain as many pointers as argc says that it contains, no more, no less. Similarly, the length of each string pointed at by argv[i] is as long as what the caller passed.

Example:

myprog.exe foo 
  • This means that argc == 2 and argv will have the length 2. This cannot be changed by your program.
  • argv[0] will point at a modifiable string "myprog.exe", size 10+1 = 11 bytes. You can change the contents but not store anything longer than 11 bytes there.
  • argv[1] will point at the string "foo", size 3+1 = 4 bytes. You can change the contents but not store anything longer than 4 bytes there.

(Curiosity: it is perfectly fine and arguably the most correct way to define argv as a VLA like this:
int main (int argc, char* argv[argc]), because argv decays into a char** anyway.)


That all being said, modifying argc and argv, although allowed by the C standard, is horribly bad practice. Don't do this. Instead you should use a local variable and let it refer to argv where needed. Example:

int main (int argc, char* argv[])
{
  const char* argstr [12] = 
  {
    "str0",
    "str1",
    "str2",
    "str3",
    "str4",
    "str5",
    "str6",
    "str7",
    "str8",
    "str9",
    "str10",
    "str11",
  };

  for(int i=0; i<argc; i++)
  {
    argstr[i] = argv[i];
  }

  /* always use argstr here, never argv */

  return 0;
}

Upvotes: 2

Jabberwocky
Jabberwocky

Reputation: 50767

You cannot mess around with argv like this. argv is not your's, it has been allocated and filled during program start according to the command line.

If you invoke your program like this:

program a b c

then argc is 4, argv[0] points to "program", argv[1] points to "a" etc.

argv[argc] is NULL

But accessing argv[5] and beyond is undefined behaviour because you access an array out of bounds.

This SO article may help too: How dangerous is it to access an array out of bounds?.

To solve your problem: don't access arrays out of bounds.

Upvotes: 1

To solve this, I created a separate char** variable, and used that variable in the code to solve the problem.

Here is what the new code looked like:

#include <stdio.h>
int main(int argc, char **argv)
{
    int nargc = 12;
    char **nargv;
    nargv = malloc(sizeof(char*)*nargc);

    nargv[0] = malloc(1 + strlen(argv[0]));
    strcpy(nargv[0], argv[0]);

    nargv[1] = malloc(1 + strlen("srt1"));
    strcpy(nargv[1], "srt1");

    nargv[2] = malloc(1 + strlen("srt2"));
    strcpy(nargv[2], "srt2");

    nargv[3] = malloc(1 + strlen("srt3"));
    strcpy(nargv[3], "srt3");

    nargv[4] = malloc(1 + strlen("srt4"));
    strcpy(nargv[4], "srt4");

    nargv[5] = malloc(1 + strlen("srt5"));
    strcpy(nargv[5], "srt5");

    nargv[6] = malloc(1 + strlen("srt6"));
    strcpy(nargv[6], "srt6");

    nargv[7] = malloc(1 + strlen("srt7"));
    strcpy(nargv[7], "srt7");

    nargv[8] = malloc(1 + strlen("srt8"));
    strcpy(nargv[8], "srt8");

    nargv[9] = malloc(1 + strlen("srt9"));
    strcpy(nargv[9], "srt9");

    nargv[10] = malloc(1 + strlen("srt10"));
    strcpy(nargv[10], "srt10");

    nargv[11] = malloc(1 + strlen("srt11"));
    strcpy(nargv[11], "srt11");

    /* Useful code */

    free(nargv[11]);
    free(nargv[10]);
    free(nargv[9]);
    free(nargv[8]);
    free(nargv[7]);
    free(nargv[6]);
    free(nargv[5]);
    free(nargv[4]);
    free(nargv[3]);
    free(nargv[2]);
    free(nargv[1]);
    free(nargv[0]);


    free(nargv);
    printf("Hello world\n");
    return 0;
}

Upvotes: 0

Related Questions