C Posix-compliant argument parsing in 42 LoC, inspired by Duff's device
github.com
github.com
...
else if (ARG_LONG("option")) case 'o': {
} ...
The idea was to leave all the decisions to the library user, so there are no automatic help pages and error messages. The library essentially just gives you a new language primitive to work with.Example usage (also included in the file):
// assumes argv and argc exist
ARG_BEGIN {
if (0) {
case 'a': a = 1; ARG_FLAG(); break;
case 'b': b = 1; ARG_FLAG(); break;
case 'c': c = 1; ARG_FLAG(); break;
case '\0': readstdin = 1; break;
} else if (ARG_LONG("reverse")) case 'r': {
reverse = 1;
ARG_FLAG();
} else if (ARG_LONG("input")) case 'i': {
input = ARG_VAL();
} else if (ARG_LONG("output")) case 'o': {
output = ARG_VAL();
} else if (ARG_LONG("help")) case 'h': case '?': {
printf("Usage: %s [OPTION...] [STRING...]\n", argv0);
puts("Example usage of arg.h\n");
puts("Options:");
puts(" -a, set a to true");
puts(" -b, set a to true");
puts(" -c, set a to true");
puts(" -r, --reverse set reverse to true");
puts(" -i, --input=STR set input string to STR");
puts(" -o, --output=STR set output string to STR");
puts(" -h, --help display this help and exit");
return EXIT_SUCCESS;
} else { default:
fprintf(stderr,
"%s: invalid option '%s'\n"
"Try '%s --help' for more information.\n",
argv0, *argv, argv0);
return EXIT_FAILURE;
}
} ARG_END;
// argv and argc now hold the non-option argumentsISO C says that:
"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."
Thus within the boundaries of defined ISO C, you can do this:
argv++; // argv pointer to array element modifiable
and you can do this: argv[0][0]++; // the string pointed to by the array is modifiable.
but not this: argv[0]++; // array itself (the pointers) not required to be modifiable
I seem to recall finding a problem like this in the K&R2 book also, probably close to thirty years ago. I don't have the book now, but the page number 117 seems to be coming up in my dim memory.Of course argv is mutable since it's a local variable/param. But what if the pointers are placed, by the OS, into read-only VM pages? Begrudgingly, the string data is made writable though, to conform to the standard.
I don't know of any platform where there are any ill effects. But the number of platforms out there with C implementations is much larger than my direct experience. Maybe this is just some historic baggage that is overdue for a revision. It was discussed in 1998, but no changes were accepted at the time.
In most coding situations, I probably wouldn't care, the same way I don't care about, say, platforms where for some reason you cannot compare pointers to distinct objects using inequality (another thing not required by ISO C). It's better to break the rules knowingly, though. You want to be badass, not dumbass.
Edit: I just saw this SO comment [0]:
> The standard calls argv consistently an array. Modifying an array refers to modifying its members. Thus, it seems obvious that the wording in the standard refers to modifying the members in the argv array, when it states that argv is modifiable.
It is still a mater of interpretation so you shouldn't depend on it.
[0] https://stackoverflow.com/questions/35105918/are-the-pointer...
The modifiability of argv was the subject of at three items in 1998 in meeting n849.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n849.htm
Someone proposed that clarifying text be added saying "Whether or not the pointers of the argv array shall be modifiable by the program is implementation-defined. If they are modifiable, they shall retain their last-stored values between program startup and program termination." This was rejected due to insufficient interest.
It was proposed that, since of course parameters are modifiable, the sentence in question be shortened to "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", omitting the mention of "parameters argc and argv". This was discussed, but not accepted.
The question was raised whether the pointers are modifiable. Response was: "This is currently implictly unspecified and the committee has chosen to leave it that way."
const char * ch;
while ((ch = GETOPT(argc, argv)) != NULL) {
GETOPT_SWITCH(ch) {
GETOPT_OPT("-a"):
aflag = 1;
break;
GETOPT_OPTARG("--bar"):
printf("bar: %s\n", optarg);
break;
GETOPT_OPTARG("-f"):
printf("foo: %s\n", optarg);
break;
GETOPT_MISSING_ARG:
printf("missing argument to %s\n", ch);
/* FALLTHROUGH */
GETOPT_DEFAULT:
usage();
}
}In addition, the compiler/vm typically can optimize them better than long if chains, depending on implementation, if that matters to you
C switch/case semantics are... not the most obvious.
The fact that the if condition is false means it won't just run the whole block straight through but you can still jump to a label in it. A goto statement would also allow you to jump into an otherwise-unreachable block.
In that case, the switch jumped to `case '-':` hidden within the macro, and the if statement is about processing long arguments (like `--help`). arguments only available as a short flag should never get executed as part of a short flag, so putting them inside the if(0) case is an easy option.
An alternative without if(0) of any variety would be:
ARG_BEGIN {
if (ARG_LONG("reverse")) case 'r': {
reverse = 1;
ARG_FLAG();
} else if (ARG_LONG("input")) case 'i': {
input = ARG_VAL();
} else if (ARG_LONG("output")) case 'o': {
output = ARG_VAL();
} else if (ARG_LONG("help")) case 'h': case '?': {
printf("Usage: %s [OPTION...] [STRING...]\n", argv0);
puts("Example usage of arg.h\n");
puts("Options:");
puts(" -a, set a to true");
puts(" -b, set a to true");
puts(" -c, set a to true");
puts(" -r, --reverse set reverse to true");
puts(" -i, --input=STR set input string to STR");
puts(" -o, --output=STR set output string to STR");
puts(" -h, --help display this help and exit");
return EXIT_SUCCESS;
} else { default:
fprintf(stderr,
"%s: invalid option '%s'\n"
"Try '%s --help' for more information.\n",
argv0, *argv, argv0);
return EXIT_FAILURE;
}
break;
case 'a': a = 1; ARG_FLAG(); break;
case 'b': b = 1; ARG_FLAG(); break;
case 'c': c = 1; ARG_FLAG(); break;
case '\0': readstdin = 1; break;
} ARG_END;
The downside is that those few short flag only arguments no longer line up nicely with the others, and they come after the default, which could be a little bit confusing.Footnote: [0] Except if that second dash is the last character of the argument, in which case -- is the end of flags marker, meaning any further arguments that begin with `-` are just funky positional paramaters
So when the character is '-', it starts just before the "if (0)" (this part is hidden within the ARG_BEGIN macro), and as you noted, it will never enter that block. However, when the character is 'a', 'b', 'c', or nothing (the end-of-string marker), it will jump directly to the corresponding "case ...:" label, even though it's within that "unreachable" block.
https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...
The C implementation does not seem to have support for optional option arguments.
The C implementation also does not seem to support "--" as a delimiter for option arguments, although that's only a guideline.
So I guess it can be used to implement POSIX compliant interfaces that don't use optional option arguments.
edit: supporting "--" as delimiter might not be a guideline:
The utilities in the Shell and Utilities volume of POSIX.1-2017 that claim conformance to these guidelines shall conform completely to these guidelines as if these guidelines contained the term "shall" instead of "should".
edit2: Seems like I was completely wrong about supporting "--", when tried out it does seem to support it. One weird corner cases is the call `<prog> -- -`, where "-" isn't interpreted as enabling stdin, but treated as a regular positional argument. `echo asd | cat -- -` reads from stdin with GNU cat.
https://9fans.github.io/plan9port/man/man3/arg.html
Generally, I think it's pretty normal to interpret "POSIX compliant" as a minimum requirement, and then extend that to your own whims as long as you don't break the POSIX part.
Yes, that was my intention.
(a)->at = realloc((a)->at, (a)->_cap * sizeof *(a)->at))
When realloc fails, the old value of (a)->at will be leaked.And memory allocation failure also leads to null pointer dereference in this code.
#unded malloc
#define malloc xmalloc
...
I suppose I could make this the default behavior, and make it overwritable.But more fundamentally, Valgrind hooks into the allocator (and the program at large) much more deeply. You could write your own allocator (many people deploy something other than libc's) and it would still figure it out.
"In C? I wonder if it's English only?"
Then proceeded to become extremely confused:
"How is this going to help me parse a complaint? It looks like arg parsing..."
^^