Initialize/set char *argv[] inside main() in one line
Asked Answered
A

3

5

I want to initialize/set char *argv[] inside the main() so that I can use argv[1], argv[2]... later in my program.


Up to now, I know how to do this in two ways:

  1. For int main(), use one line as:

    int main()
    {
        char *argv[] = {"programName", "para1", "para2", "para3", NULL};
    }
    

    Note that, using NULL in the end is because the pointers in the argv array point to C strings, which are by definition NULL terminated.

  2. For int main(int argc, char* argv[]), I have to use multiple lines as:

    int main(int argc,char* argv[])
    {
        argv[0] = "programName";
        argv[1] = "para1";
        argv[2] = "para2";
        argv[3] = "para3";
    }
    

My question is that how can I combine these two methods together, i.e. use only one line to initialize it for int main(int argc, char* argv[])? Particularly, I want to be able to do like this (this will be wrong currently):

int main(int argc, char* argv[])
{
    argv = {"programName", "para1", "para2", "para3", NULL};
}

How can I be able to do this?


Edit: I know argv[] can be set in Debugging Command Arguments. The reason that I want to edit them in main() is that I don't want to bother to use Debugging Command Arguments every time for a new test case (different argv[] setting).

Absolute answered 2/1, 2014 at 7:29 Comment(18)
Strange idea. Your case 2 has a good chance to crash the program. argc and argv should be read-only.Boyd
#4207634 This link is betterLeix
Maybe you should describe what your larger goal is. I suspect there might be a different approach that will work for you (though I'm not really sure I really understand the question, so I might be wrong).Gannes
@AlexFarber Although it's read only, I can still use new value in the mani() scope.Absolute
@Absolute Why would it be readonly?Vlada
@remyabel I mean argc is read-only. Actually, I need to set argc as well. Deleted already as not relevant to this question.Absolute
This is very clearly an X-Y problem. The variables argc and argv (or whatever you name them) are local to main() and you can do anything you want with them. The actual command-line parameters are read-only in standard C++, and if they can be accessed in a different way, it's through the OS API. What is it you're trying to solve by modifying the arguments?Placative
If you're using C++, don't muck with argc/argv directly: use a std::vector<std::string>. You can easily initialize such a vector from argc and argv (via std::vector<std::string> const args(argv, argv + argc);) or you can initialize it yourself with whatever strings you choose if you want to use a set of known strings at runtime. If you're learning C++, I'd recommend this approach. It's much safer, harder to screw up, and requires fewer asterisks.Been
@Angew The reason that I want to edit them in main() is that I don't want to bother to use Debugging Command Arguments every time for a new test case (different argv[] settings).Absolute
@Absolute This is a bad idea. Why not write a script that executes your program correctly instead of modifying the tested code every time you want to test something?Selenaselenate
@Selenaselenate Yes, it seems a bad idea in practice. But I want to be able to edit it during debugging.Absolute
@herohuyongtao: If you modify argv in main, it'll be only at compile-time, your hands are tied at debug time even then.Ils
@Ils What do you mean by only at compile-time?Absolute
I mean if you assign another string array to argv say like Emilio's solution, wouldn't you have to recompile it every time you change n_argv? If yes, then how does it help you debug, since every time you need a different string, you'll've to modify n_argv, compile and run the program. Correct?Ils
Also, most debuggers are able to assign new values to variables during debug-time. Not sure about C-Strings though.Selenaselenate
@Ils Yes. But without this, how can I debug my program by adding some breakpoints for different test cases?Absolute
@herohuyongtao: See the answer I've just posted where you don't need to worry about setting breakpoints and modifying variables on-the-fly.Ils
"Note that, using NULL in the end is because the pointers in the argv array point to C strings, which are by definition NULL terminated." – that has precisely nothing to do with the strings being NUL- (and not "NULL") terminated. You are NULL-terminating the array, not the strings. (0-termination of string literals is done by the compiler anyway.)Kolk
S
11

The safest way is probably don't write into argv referred memory, (that may not be structured as you think), but having another bulk:

int main(int argc, const char** argv)
{
    const char* n_argv[] = { "param0", "param1", "param2" };
    argv = n_argv;
    ...
}

This will distract argv from it original memory (owned by the caller, that remains there) to another that will exist for the life of main().

Note that const char* is required to avoid the deprecated "*string assigned to char**" message.


NOTE: this code had been compiled with GCC 4.8.1 on Linux giving the following results:

make all 
Building file: ../main.cpp
Invoking: GCC C++ Compiler
g++ -O0 -g3 -pedantic -Wall -c -std=c++11 -o "main.o" "../main.cpp"
Finished building: ../main.cpp

Building target: e0
Invoking: GCC C++ Linker
g++  -o "e0"  ./main.o   
Finished building target: e0
Sacchariferous answered 2/1, 2014 at 8:3 Comment(5)
Compiles as -std=C++11 -Wall -pedantic with no error with GCC 4.8.1. Also, there is no 5, in my code, so you compiled something else.Sacchariferous
The error in VS is error C2440: '=' : cannot convert from 'const char *[3]' to 'char *[]'.Absolute
Please note const char** argv in my code (not just char** argv)Sacchariferous
It worked. Thanks. Sorry for missing const stupidly. Is there a way to work through main(int argc,char* argv[])?Absolute
Not consistently: an initializer list is constant by definition. You can use const_cast<char**>(n_argv) in the assign, but in any case you are forcing a const to loose its const-ness.Sacchariferous
P
5

If you're able to use C99, you can use compound literals feature. Here's an example that seems to work when compiled as gcc -std=c99 main.c:

#include <stddef.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    argv = (char *[]){"programName", "para1", "para2", "para3", NULL};

    char **p = argv;
    argc = 0;
    while (*p++ != NULL) {
        argc++;
    }

    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
}
Peary answered 2/1, 2014 at 7:49 Comment(5)
This cannot compile in VS.Absolute
@Absolute That's why it starts with "if you can use C99," which VS doesn't support.Placative
Please note that the question is tagged C++ (not C)Sacchariferous
Note that if you do this, there are valid uses of the original argv that are invalid with the replacement argv: user code can modify the original argument strings, but it can't modify the replacement argument strings (because they are string literals).Been
My bad, don't know why I thought it's about C language. I think msvc tag is missing, it might be important as VS doesn't always conform to C++ standard.Peary
I
3

Lets say your program takes a set of strings and does something on them. Lets call it process_strings which needs to be tested. You want to pass different sets of strings for different test cases, say {"abc", "efg", "hij"}, {"thin", "run", "win"}, {"cat", "rat", "mat"}, etc. In future you want to add more such test cases without altering your original function process_strings. Then the right way to go about this problem is to modify the (driver) program such that you don't have to recompile for different test cases added/removed. An example might be:

#include <fstream>
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include <vector>
#include <iterator>

// tested function
void process_strings(const std::vector<std::string>& params)
{
    for (auto iter = params.cbegin(); iter < params.cend(); ++iter)
        std::cout << *iter << '\t';
}

// driver program
int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cout << "Insufficient data" << std::endl;
        return -1;
    }

    std::ifstream test_file(argv[1]); // pass test cases file as an argument

    std::string test_case;    
    while (std::getline(test_file, test_case))
    {
        // each line is a test case
        std::istringstream iss(test_case);
        std::vector<std::string> params;
        // break parameters separated by ' ' blankspace
        copy(std::istream_iterator<std::string>(iss),
             std::istream_iterator<std::string>(),
             std::back_inserter(params));
        // if there're valid parameters, then pass it to the tested function
        if (!params.empty())
        {
            process_strings(params);
            std::cout << std::endl;
        }
    }
}

Example of a test cases file, tests.txt:

abc efg
cat rat mat

animal man

The output produced for C:\> simple.exe tests.txt:

abc     efg
cat     rat     mat
animal  man

If you've arguments which contains space i.e. if space cannot be used as delimiter, then see this post on how to extract the arguments from the line.

Thus we've separated input data from code. Every time the test cases change, you needn't recompile the program. Just change the input, run it by the already compiled program and verify the output. This saves a lot of development time, and in some sense the basis of data-driven programming.

Ils answered 2/1, 2014 at 9:48 Comment(2)
Well, that depends on the error you're getting :) When you're asking something in a common forum, be as explicit as possible so that it's easy for others to understand you.Ils
it's hard to find a solution for this kind of error :( hmm.. i'll check moreSupercharger

© 2022 - 2024 — McMap. All rights reserved.