7

I have an assignment. The program is to print the sum of all command line arguments in C. I tried this code it compiles but throws an error after passed arguments in the console. Below is the code.

/* Printing sum of all command line arguments */
#include <stdio.h>

int main(int argc, char *argv[]) {
    int sum = 0, counter;

    for (counter = 1; counter <= argc; counter++) {
       sum = atoi(sum) + atoi(argv[counter]);
    }
    printf("Sum of %d command line arguments is: %d\n", argc, sum);
}

After compiling it outputs a Segmentation fault (core dumped) error. Your experience may solve my problem.

Below is my edited code:

/* Printing sum of all command line arguments*/
#include <stdio.h>
#include <stdlib.h> // Added this library file

int main (int argc, char *argv[]) {
    int sum = 0, counter;

    for (counter = 1; counter < argc; counter++) {
        // Changed the arithmetic condition
        sum = sum + atoi(argv[counter]);
        // Removed the atoi from sum variable
    }
    printf("Sum of %d command line arguments is: %d\n", argc, sum);
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • `atoi` is one of those C library functions that should [never be used](https://stackoverflow.com/a/46563868/584518). Also, when including library functions the format `` should be used, not `"stdio.h" (although it will work, even though incorrect). You need a better "guru". – Lundin Feb 01 '18 at 14:41
  • @Mawg Please don't recommend people to post at Code Review when their question is entirely inappropriate for that site. You are not doing anyone a favour. CR requires complete, working code, not incomplete, broken snippets. – Lundin Feb 01 '18 at 14:43
  • Ok thanks, @Lundin. My guru is not available now. So, I used stack overflow and till now I'm not aware of codereview.stackexchange.com. Next time I will use it. My guru has decades of experience in programming. He is always best. – Anudeepsyamprasad Feb 01 '18 at 14:52
  • @Lundin I just thought that I was helping a new guy; it is unlikely that he would find the site on his won (for a while anyway), and likely that he would benefit from it. I did state clearly "when your code works, post it to our sister site ...", but feel free to delete me comment. – Mawg says reinstate Monica Feb 01 '18 at 14:55
  • 3
    @AnudeepSyamPrasad Whoever taught you to use `"stdio.h"` and `atoi` is not "the best", but rather a charlatan. – Lundin Feb 01 '18 at 14:59
  • 1
    @Mawg Incorrect recommendations to post at CR is a hot potato on meta, see for example this fresh discussion: https://meta.stackoverflow.com/questions/362417/could-we-have-comments-stating-this-would-work-well-on-code-review-and-simila – Lundin Feb 01 '18 at 15:01
  • I think that you are missing the part where I said "when your code works" and imagining a part where I said "post the code above to CR". I understand that blanket recommendations of just anything to CR are not on topic, but how do you recommend we do introduce new coders to that extremely helpful site? – Mawg says reinstate Monica Feb 01 '18 at 15:04
  • I'm telling you @Lundin it's my thought, not his he always suggests syntax like this . but I like to try new ways so, I tried this don't see it in a different way but I like the way you respond. – Anudeepsyamprasad Feb 01 '18 at 15:05
  • @Lundin I followed your link to the list of functions which never should be used, and read "atoi() family of functions. These have no error handling but invoke undefined behavior whenever errors occur." atoi() seems pretty well-defined to me. What's the UB? – Bjorn A. Feb 01 '18 at 15:53
  • 2
    @Lundin *when your code works*, post it to our sister site codereview.stackexchange.com. A fine recomendation – pm100 Feb 01 '18 at 17:33
  • 1
    @Bjorn I assume that [atoi(const char* str)](https://en.wikibooks.org/wiki/C_Programming/stdlib.h/atoi) assumes that it's getting a normal null-terminated string / char*. I don't think it has any precautions against getting something that's not null terminated. Just a guess. EDIT : Looks like this is why. "However, because of the ambiguity in returning 0 and lack of thread-safety and async-cancel safety on some operating systems, atoi is considered to be deprecated by strtol." – Ian Feb 01 '18 at 17:35
  • @Ian: I agree with your comments, but Lundin said that atoi() had UB. AFAICT it does not. The 0/error ambiguity is a pain in most situations, but a total ban is a bit harsh, isn't it? – Bjorn A. Feb 01 '18 at 17:40
  • I think that he believes that because we have alternatives (the strtol() family) and because of issues with multithreading, it shouldn't be used. There are downsides to using it and there are better alternatives specifically added to fix the problem. @Bjorn A. – Ian Feb 01 '18 at 17:45
  • 3
    @BjornA. C11 7.22.1 "If the value of the result cannot be represented, the behavior is undefined." Basically if you give it anything that is not an ASCII digit, the function is guaranteed to bug out. Unlike `strtol` family of functions, that have 100% equivalent functionality, except they don't bug out. – Lundin Feb 02 '18 at 07:32
  • @pm100 Not enough. Read https://codereview.stackexchange.com/help/on-topic. – Lundin Feb 02 '18 at 07:34
  • 2
    @Ian atoi assumes it gets spoon-fed a null-terminated string consisting of nothing but valid digits. If it gets anything else, it will bug out. There is no point in using it since the `strtol` family of functions have _identical_ functionality (and more), and also proper error handling. It has nothing to do with multi-threading. – Lundin Feb 02 '18 at 07:36
  • @Lundin : Thanks for the C&V from the standard! – Bjorn A. Feb 02 '18 at 08:46
  • @Lundin Wicked, thanks! I didn't know that it was guaranteed to bug out, but I knew it assumed null termination. Won't be using that again. – Ian Feb 02 '18 at 13:57
  • @Ian Rather, there are no guarantees what-so-ever since it invokes undefined behavior. The presence of UB in a program is always to be considered as a bug though. – Lundin Feb 02 '18 at 13:58
  • @Lundin Definitely – Ian Feb 02 '18 at 14:01

5 Answers5

12

Because you are iterating until counter == argc, you are passign a NULL pointer to atoi(), It's simple, just rely on the fact that the argv array has a NULLsentinel, and do this

/* Printing sum of all command line arguments*/
#include <stdlib.h> /* For `atoi()' */
#include <stdio.h>  /* For `printf()' */

int main(int argc, char *argv[])
{
    int sum;
    sum = 0;
    for (int counter = 1; argv[counter] != NULL; ++counter)     {
        sum += atoi(argv[counter]);
    }
    printf("Sum of %d command line arguments is: %d\n", argc, sum);
}

Note that atoi(sum) is undefined behavior because sum is an int and is not a valid pointer. While atoi() will try to dereference it. Also aoti() stands for ascii to integer and sum is already an integer so the conversion doesn't make sense.

Finally, include stdlib.h for atoi(). I know you didn't include it because I have warnings enabled on my compiler and it warned me that atoi() was implicitly defined. It might work, but just because undefined behavior is that, UNDEFINED.

Also, note that there is no way to know whether the passed argument is an integer, because atoi() cannot perform error checking. You might want to use strtol() instead and check if all the values are integers.

So ... this is how you would write a more robust version of this program

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

int main(int argc, char *argv[])
{
    int sum;
    sum = 0;
    for (int counter = 1; argv[counter] != NULL; ++counter)     {
        char *endptr;
        sum += strtol(argv[counter], &endptr, 10);
        if (*endptr != '\0') {
            fprintf(stderr, "error: the `%d-th' argument `%s', is not a valid integer\n", counter, argv[counter]);
            return EXIT_FAILURE;
        }
    }
    printf("sum of %d command line arguments is: %d\n", argc, sum);
    return EXIT_SUCCESS;
}

EDIT: To address this comment

There is a possibility that argc == 0, for instance if you execute the program through one of the exec*() functions. In that case you must check before starting the loop or argv[counter] will be one element after the last, i.e. out of bounds.

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • Nice, although I have a deep founded dislike for `argv[counter] != NULL`. – Bathsheba Feb 01 '18 at 14:47
  • 1
    The only complete answer that addresses all of the bugs - nice one. – Lundin Feb 01 '18 at 14:48
  • @Bathsheba Why? The C standard guarantees that the last pointer in the list is a null pointer. – Lundin Feb 01 '18 at 14:51
  • @Lundin: I prefer `argv[counter]`, that's all: `!= NULL` is a tautology. – Bathsheba Feb 01 '18 at 14:51
  • 1
    @Bathsheba I thought you were going to say you preferred to use `counter < argc` – Chris Turner Feb 01 '18 at 14:52
  • 1
    If you wanted this to be *really* robust, you could check for potential `int` overflow (more potential for UB and such effects could manifest themselves at +/- 32767). But is this getting silly now? – Bathsheba Feb 01 '18 at 14:52
  • 2
    @ChrisTurner: No that sucks. – Bathsheba Feb 01 '18 at 14:53
  • 1
    @Bathsheba Pah. I bet you prefer that until someone writes `*argv[counter]` and gets an eternal loop, while `*argv[counter] == NULL` would give a compiler error for comparing incompatible types. It is not a tautology, it is rugged, professional code that reduces bugs. MISRA-C bans the former dangerous style for a reason. – Lundin Feb 01 '18 at 14:55
  • 1
    @Lundin, Wading slightly off topic here, `*argv[counter] == NULL` would compile in standard C++ without warning, so, really, I think this is merely kicking the can down the road of increasing obfuscation. Much of MISRA is, in my opinion, pure unbridled dogmatism. I'll get me coat. – Bathsheba Feb 01 '18 at 14:59
  • @Bathsheba As I hate `; argv[counter] ;` it doesn't let you know whether it's a pointer or an `int` or even a `bool`. Explicit comparison adds clarity to the code. – Iharob Al Asimi Feb 01 '18 at 15:04
  • @Bathsheba And to respond to your second comment, yes. That's why I said "*more* robust" instead of "*really* robust". – Iharob Al Asimi Feb 01 '18 at 15:06
  • @Bathsheba Sane C compilers implement NULL as `(void*)0` and then you get the best possible type safety as demonstrated by my example, since comparison between a integer type and a pointer is not allowed. C++ is not always safer than C - this would be a perfect example of C++ "blowing your whole leg off". – Lundin Feb 01 '18 at 15:06
  • @Lundin Real C programmers use bare 0 for the null pointer anyway, rendering the expansion of NULL moot. I'm with Bathsheba: explicit comparisons of pointers to NULL/0 are tautological, add visual clutter, and don't actually prevent bugs. – zwol Feb 01 '18 at 16:03
  • Nit: Maybe not return -1 from main()? – Bjorn A. Feb 01 '18 at 16:13
  • @BjornA. And that is because...? – Iharob Al Asimi Feb 01 '18 at 16:28
  • @IharobAlAsimi: Well, the main() function in a C program should return either EXIT_SUCCESS(0) or EXIT_FAILURE. – Bjorn A. Feb 01 '18 at 17:01
  • I downvoted because this doesnt actually explain what was wrong with the original code. OP will ikely commit the same error again later – pm100 Feb 01 '18 at 17:35
  • @pm100 I disagree, I explained every mistake. Even the missing include. I can clarify something though – Iharob Al Asimi Feb 01 '18 at 17:44
  • If `argc == 0` (and `argv[0] == NULL`), this has an out of bounds array access. –  Feb 01 '18 at 22:12
  • @hvd Under normal cirmcumstances this will not happen at all. But to be strict, I added a comment in the answer. – Iharob Al Asimi Feb 01 '18 at 23:52
  • @zwol I just proved black on white how it prevents bugs. "No" is not a valid counter-argument. Now, real C programmers actually know the difference between a null pointer, a null pointer constant and the NULL macro. [Study this](https://stackoverflow.com/a/32136460/584518). – Lundin Feb 02 '18 at 07:42
  • 1
    @BjornA. You are right, that's strictly true and correct. So I am accepting your suggestion and changing it in my code, and will keep using the macros form now on. Although note, [that sometimes you might want to return a value that indicates which error it was](https://www.gnu.org/software/libc/manual/html_node/Exit-Status.html), to use it in a shell script for example. – Iharob Al Asimi Feb 02 '18 at 12:38
  • @Lundin I cannot be bothered to refute you, but you're still wrong. – zwol Feb 02 '18 at 12:56
  • @zowl I only do it for readability, I understand that you have a preferred style but you haven't said why is *my* style bad. You say it's tautological and adds visual clutter but, in fact I think it adds clarity since you immediately know that it's a pointer comparison without having seen the variable declaration which I too like it to be at the begining if the scope where it's declared, precisely for that, to know it's scope. Also, C programmers are allowed to have their own style, and you can't say it as if anyone that doesn't follow your style is not a C programmer. – Iharob Al Asimi Feb 02 '18 at 16:44
7

It is specified that argv[argc] will always be a null pointer. You loop once too many, and pass this null pointer to atoi, leading to undefined behavior.

Change your loop conditions to be counter < argc.

And sum already is an integer, you do not need to convert it to an integer with atoi. That atoi(sum) will also lead to undefined behavior as the very first iteration will pass zero to atoi, which could be seen as a null pointer as well.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
6

The last element of argv is defined to be NULL, and the first one is always the program name. Therefore you can reduce your code to

#include "stdio.h"

int main(int argc, char *argv[])
{
    int sum = 0;
    for (int i = 1; argv[i]; ++i){
        sum += atoi(argv[i]);
    }
    printf("Sum of %d command line arguments is: %d\n", argc, sum);
}

In your code, the behaviour of atoi(sum) and what boils down to argv[argc] on the final iteration will be undefined.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
-2

A recursive version, just for the heck of it :)

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

int main(int argc, char **argv)
{   
    static int sum;

    if (*++argv == NULL)
        return !printf("sum: %d argc %d\n", sum, argc - 1);

    sum += atoi(*argv);
    return main(argc, argv);
}
Bjorn A.
  • 1,148
  • 9
  • 12
  • Just to confirm my understanding, the static int sum keeps it's value through all iterations? Also, nice :) – Ian Feb 01 '18 at 17:17
  • 1
    Thanks :) Yes, the static is used to keep adding to the same variable. – Bjorn A. Feb 01 '18 at 17:20
  • 1
    You can't call `main()` from within `main()`. The `main()` is special. – Iharob Al Asimi Feb 01 '18 at 17:43
  • Aw shit, I missed that, you're right. Throw it in it's own method and pass it the arguments and you'll be good. – Ian Feb 01 '18 at 17:47
  • @IharobAlAsimi Of course one can call main() from main(). I just did, didn't I? ;) – Bjorn A. Feb 01 '18 at 17:48
  • 2
    @IharobAlAsimi You're thinking of C++. C has no such restriction. –  Feb 01 '18 at 22:13
  • @hvd: Even if ISO Standard C++ has the restriction, there are C++ compilers that allow recursive `main` anyway. – dan04 Feb 01 '18 at 23:29
  • @dan04 That feels like a comment that should be directed at Iharob Al Asimi, not at me, but what the C++ standard says and what C++ implementations do have almost equally little relevance to a C question. –  Feb 01 '18 at 23:38
-2
/* My example. Should work, though untested. */
#include <stdio.h>

int main(int argc, char *argv[])
{
    int sum, index;    // Generally considered better form to put them on separate lines.
    sum = 0;

    if(argc > 1) {    
        for(index = 1; index < argc; index++) {
            sum += atoi(argv[index]);
        }
        printf("%d Command-line args\nSum = %d\n", argc, sum);
    } 
    else {
        printf("Not enough command-line args\n");
    }
}

Make sure to try you hardest to adhere to a specific formatting style for your code. Search for style guides if you want to define yours by name. GNU C is a good starting point. They'll make your code more readable for you, easier to debug, and help others understand what's going on in it.

Couple of corrections for your aforementioned code..

/* Your code with corrections */
#include "stdio.h"

int main(int argc, char *argv[])
{
   int sum=0,counter; 
   // 1. Normally you want to give each variable it's own line. Readability is important.
   // 2. Don't initialize on declaration either. Refer to my example.
   // 3. Always add spaces between operators and at the end of statements for better readability.
    for(counter=1;counter<=argc;counter++)
    {
       // 4. atoi(sum) is unneccessary as it interprets your int sum as a const char*, 
       // which as far as I can tell should just give you back it's ascii value. 
       // It might be undefined however, so I would remove it regardless.
       // 5. Your segfault issue is because you iterate over the end of the array.
       // You try to access the [argc] value of the array (remember 0-based
       // indexing means a size val is always 1 greater than the greatest index).
       // Make 'counter<=argc' into 'counter < argc'

       sum = atoi(sum) + atoi(argv[counter]);
    }
  printf("Sum of %d command line arguments is: %d\n", argc, sum);
}

atoi() documentation of a form.

Be forewarned, your code will still function if you input chars or strings into the command line, but the behavior will be odd. atoi() will convert the char to it's corresponding ASCII decimal character index/value.

You can also use a null value (argv[] is always terminated with a NULL value at argv[argc]) to end iteration. I prefer using the provided argc though.

Hopefully this helps you a little bit. If you don't understand anything I threw up here, comment or search it online.

Cheers!

Ian
  • 675
  • 1
  • 9
  • 25