38

A comment on this answer to a StackOverflow question made me curious. According the C99 standard, section 5.1.2.2.1:

The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:

int main(void) { /*...*/ }

or with two parameters (referred to here as argc and argv,though any names may be used, as they are local to the function in which they are declared):

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

or equivalent; or in some other implementation-defined manner.

This was the case in ANSI C as well, it seems; I'm unaware of any earlier standards besides K&R.

The second signature, with two parameters, has always included argc, the number of arguments in argv. Given that argv is a NULL-terminated list, however, it's trivial to calculate this number.

Historically, is there a reason for including argc as a parameter of main? Was it a feature of earlier languages such as B? Was it to save computation (at the cost of potentially using a bit more memory)? Was it just a programmer convenience?

1
  • 1
    A common extension is a third parameter, char *envp[] (or equivalently char **envp). There is no count parameter associated with it, it's just a pointer to the first element of a NULL-terminated array of pointers. Dec 15, 2017 at 19:54

5 Answers 5

47

K&R first edition (1978) does not mention the NULL terminator in the argv list at all. This was added later, in ANSI C. The relevant paragraph (section 5.11, p. 110) is:

In environments that support C, there is a way to pass command-line arguments or parameters to a program when it begins executing. When main is called to begin execution, it is called with two arguments. The first (conventionally called argc) is the number of command-line arguments the program was invoked with; the second (argv) is a pointer to an array of character strings that contain the arguments, one per string. Manipulating these character strings is a common use of multiple levels of pointers.

Of course, when the terminating NULL was added to the standard, there was no possibility of changing the number of arguments passed to main.

38

The requirement that argv[argc] be NULL was added in the ANSI C standard; it wasn’t a requirement before then, and some environments didn’t null-terminate the array (the strings themselves were of course null-terminated). In fact, in Unix V4, argv[argc] was documented as being -1 and not 0. Thus before ANSI C, argc was necessary, and it was kept thereafter for backwards compatibility.

13
  • 11
    @tonysdg Even today the fact that the argv array is NULL terminated isn't well known. It wouldn't surprise me if there were a number of pre-ANSI non-Unix C compilers that didn't do this. A different but related issue is that a lot of programs assume argc can never be 0 despite this not being guaranteed.
    – user722
    Dec 13, 2017 at 20:04
  • 4
    @Wumpus right, it’s a mixture of calling convention from the kernel, C library and compiler — main isn’t the real entry point into a program, there’s C library code that’s executed first and can massage the arguments to main. Dec 13, 2017 at 20:42
  • 2
    @StephenKitt I may be wrong, but I think main was the real entry point back then. The stuff that happens before main nowadays is all stuff that didn't exist until later.
    – user5152
    Dec 13, 2017 at 20:43
  • 4
    @WumpusQ.Wumbley Nop. The real entry point of a Unix programm was always the first word. This is where the linker puts the C-lib, which in turn, after seting up various stuff (like file I/O tabels or parsing aguments), thenn calls main() with argc,argv.
    – Raffzahn
    Dec 13, 2017 at 20:47
  • 5
    @Wilson On Unix systems you just need to do execl("/path/to/program", NULL). (POSIX says "The argument arg0 should point to a filename string that is associated with the process being started", emphasis mine, so NULL is allowed here but discouraged.) More generally it's easy to imagine an implementation of C on some platform (eg. embedded) without any concept of command line arguments and so argc is always 0.
    – user722
    Dec 13, 2017 at 21:33
14

Originally the argv[argc] element did not exist in Unix / K&R C.

Adding a NULL pointer is a later feature added against 'lazy' programming. This still doesn't make it a 'NULL-terminated' List, as there is no guarantee that the list doesn't already contain a NULL pointer due whatever previous processing.


argv is by definition an array (vector in BCPL notation) of string pointers with argc elements thus only indices of 0..[argc-1] are valid. K&R's original The C Programming Language from 1978 includes only examples (chapter 5.11) where argc is used to determinate boundries.

The same is true for all examples in 1988's second edition of The C Programming Language. No usage of argv[argc]. But now a new half sentence is added on page 115: '...additionally, the standard requires that argv[argc] be a null pointer.' followed by an ilustration of an example vector. The 'standard' referenced here is the proposed ANSI standard which was basicly finished, but not published until 1989 (C89).

Before that, the X/Open group (holder of the Unix trademark back then) tried to define what Unix or better a unixoid environment should be by creating several standard books called XPG (X/Open Portability Guide). XPG2 from 1987 (?) defines among others the C programming language interface including the C-lib which in turn prepares argc/v when calling main(). Here a new, last NULL element for main's argv was included.

The X/Open definitions are the most important source that went into C89. So it may be safe to see this as a point where it 'officially' entered C. Now which of the Unix(oide) systems did bring it into XPG2 is not fully known to me. AFAIK it is based on an implementation for some BULL system of the early 80s.


P.S.: And while it may be 'easy' to calculate the number of arguments by runing along the list until the first NULL is encountered, this is neither secure, nor free, but costs processing time. So why should a (user) program spend many cycles to calculate that number, when the calling program (OS/C-Lib) has already done the job while preparing the list? Remember, once upon a time programming was sensible and about performance :))

5
  • The idea of null-terminated lists of pointers pre-dates K&R C since Unix V4’s exec already uses them. (I’m not saying you’re saying it doesn’t, just commenting.) Dec 13, 2017 at 20:48
  • @StephenKitt Erm, I didn't say Null-terminated lists in generl are some new inventions. it's only about how argv and C
    – Raffzahn
    Dec 13, 2017 at 20:49
  • 1
    Sorry, I misinterpreted “Adding a NULL pointer is a later added feature against 'lazy' programming.” as a general comment, rather than specific to argv! Dec 13, 2017 at 22:12
  • 11
    "argv is by definition an array (vector in BCPL notation)" Now we know why it's argv instead of arga...
    – RonJohn
    Dec 14, 2017 at 1:00
  • 1
    I had always assumed v for values. Dec 15, 2020 at 9:56
3

Looking back in the context of recent ideas in programming languages, we can now reason it in more than one ways.

(1) Precondition Validation and ease of use: Even if it's a NULL terminated list, just to know how many arguments are passed in, one has to go through the entire list to count the number, and every program that uses command line parameters needs to do that to work correctly. Before proceeding with checking the validity of the arguments passed in, and then with their eventual use, the program needs a precondition that terminates the program perhaps with a 'usage help' statement if the number of arguments sent in does not match with the number of arguments expected. So a 'ready-made' argc is quite handy.

(2) Consistent Style: Secondly, arrays are raw in C, and the program cannot find out the size of an array given the array pointer variable unless some sort of termination is encoded as a special value held inside the array. Such a special value may not always be possible if one wants to use the entire range of the type of the values being held in the array. The use of argc is consistent with this primitive choice.

(3) Arrays are not self-aware: Languages that treat arrays as objects that know their own cardinalities do not need argc. So, only they could drop the parameter altogether. This is not the case with C.

0

The early Unix evidence is inconclusive, but nevertheless I'm offering this answer as supporting material.

See this 1971 Unix Programmer's Manual, in particular page SYS EXEC (II), i.e., the man page for the exec system call.

The synopsis for the call shows that the caller of exec is expected to provide a zero-terminated list of string pointers.

    sys exec; name; args
     …
name: <…\0>
      …
args: arg1; arg2; …; 0
arg1:  <…\0>
      …

Clearly, the null is needed here because there is no count.

However, the description of what the exec'd image sees does not include the null terminator. It does however include an argument count.

sp->  nargs
      arg1
        …
      argN
arg1: <arg1\0>
        …
argN: <argn\0>

(addresses increase down the diagram).

I think this is "1st edition Unix" since there's a slightly later version that explicitly calls itself "2nd edition". This is an unmapped PDP-11/20; the 2nd ed. manual anticipates the future support for an 11/45 (which can support an MMU).

So, the relevance to the question? I think it's self-evident that the C convention would follow exactly the "bare Unix" convention, and that there would be a null terminator pointer in the exec'd process. However, there's no actual evidence in the manual that the terminating null pointer was present.

But there is evidence that the argument count was there from the start. So, either the argument count came first and the null pointer was added later for some reason, or else the argument count and the null pointer were both there all along.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .