2

I tried to add an argument to main by scanf. But it didn't work. The following is my program. My question is: what's wrong with this program? And is it possible to add an argument to main by scanf() instead of in the command line?

#include <stdio.h>
#include <errno.h>

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

    /*check if there is no argument, that is, argc!=2*/
    if(argc != 2){

        puts("Please enter an argument: ");
        scanf("%s", &argv[1]);

        printf("\nYou've entered argument: %s\n", argv[1]);
        return 1;
    }

    printf("\nYou've entered argument: %s\n", argv[1]);

    return 0;
}
OldProgrammer
  • 12,050
  • 4
  • 24
  • 45
Bing Du
  • 61
  • 2
  • 2
    No, you cannot do that. Normal validation practice is that if arguments are missing, print out a "usage" message with parameter definitions and exit. – OldProgrammer Jun 18 '15 at 01:06
  • 2
    You need to use local variables, you cannot write `argv[1]` if `argc < 2` – Ôrel Jun 18 '15 at 01:06
  • Separate issue: when using scanf for strings, you don't need the & if you are passing in the address of a string. – samgak Jun 18 '15 at 01:17
  • @samgak in this code `argv[1]` is a `char *`, not a string – M.M Jun 18 '15 at 01:23
  • If you've got a suitable POSIX-2008 compliant version of `scanf()`, you could use `scanf("%ms", &argv[1])`; otherwise, the `&argv[1]` is incorrect. You probably should not try to read an argument if the user supplied more than two arguments. You could allocate memory and pass that to `scanf()` and then set `argv[1]` to point to that memory -- it'll even be safe as long as `argc` is at least one (which it normally is) as `argv[argc]` exists and contains a null pointer. However, your argument list would no longer be terminated by a null pointer; beware code that expects it to be. – Jonathan Leffler Jun 18 '15 at 01:25
  • @MattMcNabb Sure. I didn't mean the C++ string type though, since this is C. – samgak Jun 18 '15 at 01:26
  • @JonathanLeffler is it possible to detect in the code whether `%ms` is available? – M.M Jun 18 '15 at 01:29
  • @MattMcNabb: not really. I guess you could try: `char *pointer = 0; if (sscanf("abcdef", "%ms", &pointer) == 1 && pointer != 0) { free(pointer); ...use %ms with a modest degree of confidence... } else { ...assume %ms is not available... }`. But that's treading on thin ice! Very thin ice. – Jonathan Leffler Jun 18 '15 at 01:33
  • @JonathanLeffler "You could allocate memory ... and then set argv[1] to point to that memory" but you'd be shot on sight for doing it, surely? – jarmod Jun 18 '15 at 01:33
  • @jarmod: not necessarily. As long as you don't go outside the range of the allocated array, you can modify `argv`. You need to be very aware of what you're doing. It, too, is thinnish ice; there are ways of shooting yourself in the foot, and that breaks the thin ice you're standing on. But if you are careful, you are not treading out of bounds. (You probably couldn't safely use `getopt()` on the modified array if you overwrote `argv[argc]` with a non-null pointer, for example -- it might look for a null terminating pointer, though it is told the length of the array so it might be OK, just!) – Jonathan Leffler Jun 18 '15 at 01:36
  • Also, if `argc == 0` then `argv[1]` causes UB. so the test `argc != 2` needs to have at least `argc != 0` included – M.M Jun 18 '15 at 01:39
  • @JonathanLeffler I was hoping there might be a macro available that specifies whether the feature is available or if the library is a certain version – M.M Jun 18 '15 at 01:41
  • @MattMcNabb: Maybe `_POSIX_VERSION`? Otherwise, no. – Jonathan Leffler Jun 18 '15 at 01:44
  • @JonathanLeffler If you think `scanf("%ms", &argv[1])` is OK, you may to answer http://stackoverflow.com/q/25737434/2410359 – chux - Reinstate Monica Jun 18 '15 at 01:58
  • @chux it seems pretty inconclusive.. I'd hazard a guess that the standard writers don't know either but don't want to clarify it because there might already be both code out there that writes argv[n] and compilers that assume it's not writable – M.M Jun 18 '15 at 02:14
  • @Matt McNabb Agreed. I further see little need for messing with `argc, argv`. Recommend posting your thoughts as a wiki answer to [Is argv n writable?](http://stackoverflow.com/q/25737434/2410359) - maybe on its anniversary? – chux - Reinstate Monica Jun 18 '15 at 02:29

3 Answers3

2

Yes, code can effectively add arguments.

"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." C11dr §5.1.2.2.1 2

So this allows code to change argc and argv and change argv[0][0] (if argc > 0) - but wait, there's more...

Can code change the original elements of argv[], like argv[1], etc? That is an open question still hanging in SO here. So let us avoid that.

The way to change the contents of argv[] is to first create an alternate set argv_alt[] array and then assign argv_alt --> argv.

#include <stdio.h>

int main(int argc, char *argv[]) {
    // Assume argc == 1
    printf("%d '%s'\n", argc, argv[0]);
    char a[3][10] = { "abc", "def", "fgh" };
    char *argv_alt[] = { a[0], a[1], a[2], NULL };
    argc = 3;
    argv = argv_alt;
    printf("%d '%s' '%s' '%s'\n", argc, argv[0], argv[1], argv[2]);
    return 0;
}
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

The %s specifier in scanf expects that you pass in a pointer which points to space that has already been allocated and is big enough to hold whatever will be read.

Instead you pass in a pointer which points to an area where another pointer is stored. This causes undefined behaviour because the pointer you pass in has the wrong type.

It's not possible to extend the length of the argv array. In fact, writing to argv[n] probably causes undefined behaviour anyway.

Instead you should just read into a variable inside main.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
0

Argv is allocated by the system when the user types in her arguments. If there wasn't arguments you cannot use the position 1 on that array. Although, as pointed out on the comments, you shouldn't do the way you are willing to, you could scanf arguments if you declare your own local variable.

prmottajr
  • 1,816
  • 1
  • 13
  • 22