Can argc be zero on a POSIX system?

前端 未结 6 1387
暖寄归人
暖寄归人 2021-02-04 23:04

Given the standard definition for the main program:

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

Under which circumstances can argc

6条回答
  •  失恋的感觉
    2021-02-04 23:36

    TL;DR: Yes, argv[0] can be NULL, but not for any good/sane reason I know of. However, there are reasons not to care if argv[0] is NULL, and to specifically allow the process to crash if it is.


    Yes, argv[0] can be NULL on a POSIX system, if and only if it was executed without any arguments.

    The more interesting practical question is, should your program care.

    The answer to that is "No, your program can assume argv[0] is not NULL", because some system utilities (command-line utilities) either do not work or work in a non-deterministic fashion, when argv[0] == NULL, but more importantly, there is no good reason (other than stupidity or nefarious purposes) why any process would do that. (I'm not sure if the standard usage of getopt() also fails then — but I would not expect it to work.)

    A lot of code, and indeed most examples and utilities I write, begin with the equivalent of

    int main(int argc, char *argv[])
    {
        if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            printf("Usage: %s [ -h | --help ]\n", argv[0]);
            /* ... print usage ... */
            return EXIT_SUCCESS;
        }
    

    and this is reasonable and acceptable, because there is no good reason for a process to exec another process without providing at least the command path being executed, i.e. execlp(cmd, cmd, NULL) rather than execlp(cmd, NULL).

    (However, I can think of a few nefarious reasons, like exploiting timing race windows related to pipe or socket commands: an evil process sends an evil request via an established Unix domain socket, and then immediately replaces itself with an authorized victim command (run without any arguments, to ensure minimum start-up time), so that when the service getting the request checks the peer credentials, it sees the victim command, instead of the original evil process. It is, in my opinion, best for such victim commands to crash hard and fast (SIGSEGV, by dereferencing a NULL pointer), rather than try and behave "nicely", giving the evil process a larger time window.)

    In other words, while it is possible for a process to replace itself with another but without any arguments causing argc to be zero, such behaviour is unreasonable, in the strict sense that there is no known non-nefarious reason to do so.

    Because of this, and the fact that I love making life hard for nefarious and uncaring programmers and their programs, I personally will never add the trivial check, similar to

    static int usage(const char *argv0)
    {
        /* Print usage using argv0 as if it was argv[0] */
        return EXIT_SUCCESS;
    }
    
    int main(int argc, char *argv[])
    {
        if (argc < 1)
            return usage("(this)");
        if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
            return usage(argv[0]);
    
        /* argv[0] and argv[1] are non-NULL, argc >= 2 */
    

    except if requested by someone with a particular existing use case in mind. And even then I'd be a bit suspicious, wanting to verify the use case myself first.

提交回复
热议问题