That document does not describe long options. I guess that can be treated as a conforming extension.
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.
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.
In the context of option parsing, "POSIX compliant" makes me think of the semantics of getopt(3): https://pubs.opengroup.org/onlinepubs/9699919799/functions/g... Notably, the utility conventions and even the guidelines (guideline 14 presumes non-compliance with guideline 7) support optional option-arguments, while the bundling semantics of getopt preclude optional option-arguments. Utilities, including POSIX-defined utilities, which have optional option-arguments cannot use getopt.
PSA: Read the "simplified explanation" near the bottom of the wiki page (https://en.wikipedia.org/wiki/Duff%27s_device#Simplified_exp...) before looking at the actual code. I always found Duff's device confusing to understand (i.e. walk-through in my mind) and this section was what made it click.
Because there's really a switch statement (hidden behind a macro) that will jump to labels within that block.
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.
The whole `if` portion is for if the second character of the argument (after the initial `-`) is a second `-` [0].
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
The way "switch" works in C is very weird. It behaves more like a "goto", where each "case ...:" is a label to which it can jump.
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.
This uses switch abuse to handle short and long arguments in one place:
...
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 arguments
Very neat. Do you implement option parsing in "magic getopt" or can you (somehow?) handle setting up the option string arguments used by the more familiar variants in getopt(3)?
All the option parsing is done in functions called by the macros. The "getopt string" isn't needed since the GETOPT_OPT() and GETOPT_OPTARG() "case statements" convey the same information (the set of valid options).
Ah, my way of dealing with memory allocation failure is to write a xmalloc/xcalloc/xrealloc that exit on error. So the idea would be that the library users can just:
#unded malloc
#define malloc xmalloc
...
I suppose I could make this the default behavior, and make it overwritable.
Firstly, an xmalloc will call malloc and add error checks. So malloc is still called.
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.
I realized that I essentially never use a "switch" statement. It seems like a control-flow construct that made sense in the 1970s to help the compiler generate jump tables, but doesn't seem particularly useful now. Moreover, it seems error-prone with accidental fall-through. And doing "fancy" things with it makes code very hard to read. Does anyone else find "switch" kind of redundant?
This invokes UB, unfortunately. It works where it works.
ISO 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.
I'm skeptical there's a single C compiler out there that makes the first two behave as expected but not the third. In fact, I wouldn't be surprised if the real oversight is that the standard meant to say all three, or that the authors thought it implied.
It's not necessarily up to a compiler, or its program setup stub code, but possibly up to the host environment.
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.
Another thing to consider is that the pointers may be writable, but changing them causes harm. E.g. suppose argv[] is prepared by a function which dynamically allocates each pointer, and then the run-time calls free on it when the program exits. According to ISO C, that would be conforming.
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.
Oh, wow thanks. I made sure to check the strings are modifiable, but that *argv may not be modifiable never occurred to me, I suppose this is an easy enough fix, I'll look into it later.
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.
I don't see the interpretation issue. The "parameters argc and argv" are the scalar, local variables. "argv array" is the object that "parameter argv" points to; it is not a parameter.
The modifiability of argv was the subject of at three items in 1998 in meeting n849.
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."
55 comments
[ 2.7 ms ] story [ 117 ms ] thread"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..."
^^
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.
https://en.wikipedia.org/wiki/Duff%27s_device
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:
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.
Example usage (also included in the file):
https://github.com/Tarsnap/libcperciva/blob/master/util/geto...
BSD licensed, of course.
And memory allocation failure also leads to null pointer dereference in this code.
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 addition, the compiler/vm typically can optimize them better than long if chains, depending on implementation, if that matters to you
ISO 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:
and you can do this: but not this: 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."