2

I'm calling a simple executable as follows:

$ ./test -l 123

I have some relatively simple code that checks whether there are enough args provided, and if not prompts the user to enter the value instead.

The code looks like this:

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

int main(int argc, char *argv[])
{
    long max_number;
    char out[1024];

    // Check whether max_number was passed via args
    if (argc < 2) {
        printf("Enter the maximum number: ");
        fflush(stdout);
        scanf("%d", &max_number);

    } else {
        for (int i=1; i<argc; i++) {
            sprintf(out, "%sArg %d of %d is '%s'\n", out, i, argc, argv[i]);

            if (strcmp(argv[i], "-l") && argc > i) {
                sprintf(out, "%sFound '-l' argument at index %d\n", out, i);
                int argTarget = i + 1;
                sprintf(out, "%sLooking for the maximum number at argument %d\n", out, argTarget);
                max_number = atoi(argv[argTarget]);
            }
        }
    }

    printf("%s", out);
    printf("Maximum number received: %d\n", max_number);

    return 0;
}

What I would expect to come from the command above is:

Arg 1 of 3 is '-l'
Arg 2 of 3 is '123'
Found '-l' argument at index 1
Looking for the maximum number at argument 2
Maximum number received: 123

What actually happens is this:

Arg 1 of 3 is '-l'
Arg 2 of 3 is '123'
Found '-l' argument at index 2
Looking for the maximum number at argument 3
Maximum number received: 0

So for some reason, despite the fact that I can get the value from index i no problem, when I try to get the value of i it seems to be one higher than it's supposed to be. I don't understand why it's correct when I check it on the first line of the for loop, but when I check it after the strcomp() it seems to be incorrect.

Can anyone help me understand what's going on here?

I'm using GCC on Windows 10 and running in a MinGW-64 terminal. To compile I'm simply running:

$ gcc args.c -o args
Engineer81
  • 1,004
  • 1
  • 11
  • 26

1 Answers1

3

strcmp returns 0 if the strings are equal. Therefore:

if (strcmp(argv[i], "-l") && argc > i) {

should be:

if (strcmp(argv[i], "-l") == 0 && argc > i) {

Also, note that there are a few other issues with the code:

  • strings.h should be string.h
  • No need for out -- remove it and use printf instead. You are currently breaking aliasing, since you pass out to both the output and the input of sprintf.
  • Use the proper printf format string.

Try to compile your code with full warnings enabled! This is the code without warnings:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int max_number;

    // Check whether max_number was passed via args
    if (argc < 2) {
        printf("Enter the maximum number: ");
        fflush(stdout);
        scanf("%d", &max_number);
    } else {
        for (int i=1; i<argc; i++) {
            printf("Arg %d of %d is '%s'\n", i, argc, argv[i]);

            if (strcmp(argv[i], "-l") == 0 && argc > i) {
                printf("Found '-l' argument at index %d\n", i);
                int argTarget = i + 1;
                printf("Looking for the maximum number at argument %d\n", argTarget);
                max_number = atoi(argv[argTarget]);
            }
        }
    }

    printf("Maximum number received: %d\n", max_number);

    return 0;
}
Acorn
  • 24,970
  • 5
  • 40
  • 69
  • 1
    Of course it does! What an idiot! That sorted it straight away. Now I feel dumb! :D Thanks for your help – Engineer81 May 17 '18 at 12:53
  • 1
    You can also use `if ( !strcmp(argv[i], "-l") && argc > i) {` – Andrew Henle May 17 '18 at 12:57
  • 1
    @AntTheKnee: Added some more comments on your code, in case it helps you! :-) – Acorn May 17 '18 at 12:58
  • 1
    Thanks @Acorn. A couple of points, though: 1. I'd used `fflush()` because MinGW-64 seems to like to cache output so things end up out of order 2. The `sprintf()` were in there for debugging as I couldn't quite work GDB out well enough to understand what was happening. I know, amateur :) – Engineer81 May 17 '18 at 12:59
  • 2
    Use `out` in the input and output of `sprintf` does not break any aliasing rule (the same name is used, so there is no alias). It violates C 2011 [N1570] 7.21.6.6 2, which says, of `sprintf`, “If copying takes place between objects that overlap, the behavior is undefined.” – Eric Postpischil May 17 '18 at 13:04
  • 1
    Thank you for the clarification @EricPostpischil. The actual code that I'm using has no output during that stage at all so I've just removed it. I was just trying to concatenate `sprintf()` output so that I could be sure that there was no caching going on anywhere as that had tripped me up previously. – Engineer81 May 17 '18 at 13:05
  • 1
    @AntTheKnee: Actually, you are right; the C library can be buffering it, so keep the `fflush`. – Acorn May 17 '18 at 13:05
  • @EricPostpischil But the parameters for `sprintf` are marked as `restrict`, so you cannot pass the same pointer, no? -- e.g. see the example 3 at 6.7.3.1.8 (on N1548): "if an object is accessed through one of the pointer parameters, then it is not also accessed through the other." – Acorn May 17 '18 at 13:15
  • @Acorn: It is, as I noted a violation of rules in the C standard, but it is not a violation of the *aliasing* rules *per se*. As footnote 88 in C 2011 [N1570] says, clause 6.5, paragraph 7 is intended to specify the circumstances in which an object may be aliased, and passing `out` to `sprintf` as both input and output does not violate those. The common English definition of “alias” is an alternate identity for something, and the aliasing rules prohibit referring to the same object with both an `int` and ` float` identity, for example. That is not happening when… – Eric Postpischil May 17 '18 at 13:24
  • … you use `out` both as a `char` array for the `sprintf` output and as a `char` array for the `sprintf` input. It is the same identity of the same object, so there is no aliasing violation. It is a violation of different rules, not aliasing rules. – Eric Postpischil May 17 '18 at 13:24
  • @EricPostpischil I see what you mean, thanks; however, [gcc (since 7.0) uses that terminology in `-Wrestrict`](https://godbolt.org/g/z8B2Vz): "*passing argument N to restrict-qualified parameter aliases with argument M*". Which word would be better there to avoid confusion? As for the `sprintf` topic: what I was referring to is that, even if 7.21.6.6 did not explicitly note the undefined behavior about overlapping objects for `sprintf`, it would be undefined behavior anyway due to the `restrict` rules on 6.7.3 (because there is both a write and a read). – Acorn May 17 '18 at 13:36
  • Also, on C11's 6.7.3.1.10 (example 3): "...illustrate how an unmodified object can be aliased through two restricted pointers.", on the topic of `restrict`. Same for the [`gcc` docs](https://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html). – Acorn May 17 '18 at 13:47
  • @Acorn: I do not think the `restrict` rules would apply. The formal definition of `restrict` in 6.7.3.1 applies to the block associated with a function definition. But no definition for `sprintf` appears in the source code of a C program. Its behavior is defined by the C standard, not by source code. I think that is why the explicit statement appears in 7.21.6.6 2—since the rules about `restrict` do not cover how standard library routines behave, it is explicitly stated that you cannot do this. – Eric Postpischil May 17 '18 at 13:47
  • @Acorn: As for terminology, the formal definition of `restrict` is more mathematical than natural language, so it does not provide much good terminology for this. We could say that `sprintf` (in effect, if not in explicit source code) accesses `out` through a pointer not **based** on the restricted pointer. Properly, to be pedantic about the compiler message, it ought to say, in regard to `sprintf`, that the third argument violates the constraint about overlapping objects. – Eric Postpischil May 17 '18 at 13:50
  • @EricPostpischil: Hum... while it indeed applies to the block, the standard remarks in 6.7.3.1.9: "*The cost is that the programmer has to examine all of those calls to ensure that none give undefined behavior.*". It seems to me that they intended that the caller is meant to be aware of the qualifier. Now, of course, for functions that you only have the declaration, it is unclear to me whether that should mean anything (since you cannot know how the pointers are used inside it anyway -- like the example at 6.7.3.1.9 explains). (...) – Acorn May 17 '18 at 14:14
  • (...) On the other hand, one can reasonably expect how they are supposed to be used (e.g. in the case of `sprintf`); and a caller can anyway always be on the safe side by never passing aliasing pointers. Also, if it was not meant to signify anything for functions for which we only have the declaration, why would they specify `sprintf` and other functions in the standard with `restrict`-qualified pointers? It would seem an implementation detail. (...) – Acorn May 17 '18 at 14:18
  • (...) Moreover, in the case of `sprintf`, since they are anyway stating explicitly what is undefined behavior, I would say it does make even less sense to mark the parameters as `restrict`. So I am not sure -- maybe I should post this as a question. As for the terminology, I would say that, at least de-facto, aliasing is a good term in this context, given that the standard and at least a compiler uses it to mean "different pointers to the same object". – Acorn May 17 '18 at 14:24
  • Posted the question here: https://stackoverflow.com/questions/50395436/does-c11-restrict-type-qualifier-imply-anything-for-functions-without-definition – Acorn May 17 '18 at 15:56
  • Wow. I didn't expect a simple debug statement to create such an interesting discussion! – Engineer81 May 18 '18 at 09:01