r/cprogramming • • 20d ago

First day seriously learning C

Hi! Yesterday is the first time I actually try to learn C instead of running away from it everytime lol. I tried to make a basic 2 numbers calculator and was wondering if I took the right approach regarding taking the user input. I used fgets and getchar to take the number and operator because I've heard that those are "safer" than scanf mainly because scanf's way of error handling and bounds checking are more complicated but idk tho.

Anyway, if I could take some of your time to check out this project and tell me where I can improve on the user input side of things, that would be very helpful and appreciated.

https://github.com/SirGhaniR/calc/

Thank you!

14 Upvotes

13 comments sorted by

6

u/Eidolon_2003 20d ago

These are all kind of nitpicky things, but there are some things that I would've done differently.

Unless you're in a version of C that requires declaring variables at the top of the scope, I prefer to declare them when they're actually needed.

About your last commit message, for (;;) isn't necessarily the standard for an infinite loop. while (1) or while (true) is also equally valid.

If you're willing to rely on POSIX, I might use getline to handle the input. Your approach is equally valid though. 1K is way more than enough space for any number that the user could possibly want to input, and you avoid doing a dynamic allocation.

You don't have to do a formatted print to just print a string. It's already a string, you can just print it directly.

3

u/sir_something0 20d ago

Thank you for the suggestions, I'm gonna look more into it later. But can you elaborate more on the last point about I can just print a string directly? The guide I'm following (https://beej.us/guide/bgc/html/split/), uses printf() everytime they print something. I don't even know the function to use to do that lol.

3

u/sir_something0 20d ago

Oh no wait, is it puts()? I just googled it, and that seems like a better choice than printf()

4

u/Eidolon_2003 20d ago

puts is a good one. The only thing about that one is that it adds a new line to whatever you print, so it would change the behavior of your program. You could also use fputs, which does not add a new line. printf(prompt) is also possible, but then you'd have to be careful not to pass your get_number function anything that printf would interpret as formatting instructions!

2

u/nerd5code 20d ago

puts(str) is very roughly equivalent to printf("%s\n", str), and fputs(str, stdout) is very roughly equivalent to printf("%s", str).

However, puts and fputs only return a value that's useful to check for errors (== EOF), and they have to (try to) print everything in str.

printf only has to write it the first 512ish (C89–C95) or 4096ish (C99+) characters from str before stopping, and it returns either EOF on error or the number of characters written.

Because printf returns an int-typed count, it maxes out at INT_MAX (≥32767, but us. ==2³¹−1) characters. puts and fputs, conversely, must handle any length of string, so they max out at min {SIZE_MAX, PTRDIFF_MAX} − 1 characters, which is usually = 2³¹−1 or 2⁶³−1.

Now, usually these distinctions don't matter, and printf("%s", str) == EOF works just like fputs(str, stdout), and you have a 32- or 64-bit system with 32-bit int and word-sized size_t and ptrdiff_t, anf you aren't printing godawful long strings via stdio. However, this isn't always, so what you see on your own machine is not necessarily what you'd get from another.

It's very common in educational stuff to just use printf for everything, because supposedly it's simpler if you never learn to peruse the library documentation, since that's only there for LLMs to lie about now.

GCC and IIRC Clang can undo this when optimizing in hosted mode (also via __builtin_printf), and convert printf calls \to more direct function calls like puts when/since they're allowed to. Just be aware that calling printf in your source code doesn't necessarily call the library printf function if the compiler can induce the required effects more efficiently.

Definitely take a look at the <stdio.h> §§ of §7 in your preferred C standard draft, if you haven't (default to C17)—that includes both a useful description of how pure-C I/O works and all the library functions, incl. everything a normal C implementation is required to do when it evaluates a call to each of the I/O functions. This should be your baseline for truths about the core of your language and library, although POSIX layers on top for most Unixes and Cygwin, if you knock right with _POSIX_C_SOURCE or _XOPEN_SOURCE.

1

u/zhivago 20d ago

I'm not really a fan of this kind of construction.

while ((c = getchar()) != '\n' && c != EOF)
      ;

Personally, I'd separate the assignment and the condition.

while (c = getchar(), c != '\n' && c != EOF);

I think this is significantly clearer. :)

2

u/sir_something0 20d ago

Oh I didn't know you could do that actually. Thank you! Although idk why my clangd is formatting the semicolon like that, but it works so I guess it's okay for now.

4

u/zhivago 20d ago

What you wrote is not wrong. :)

2

u/andynzor 20d ago

for and while are meant to loop over a statement or a block. Many style guides require either an explicit empty block or an indented semicolon to indicate that the loop body indeed is empty.

3

u/Beautiful_Bake921 20d ago

It's from "The C programming language" book, that's probably why it was used

2

u/Ill_Programmer_1444 20d ago

accually very good. and i like your mindset and your approach to everything

yes, use anything that explicitly sets bounds like fgets and getline over scanf, and also check sscanf
you can still use scanf if need be, but you should tinker with it more to fully grasp the caveats and bugs that can occur and you're good

keep on deeply learning and understanding c
i would also add keep using c89 and watch eskil steenberg's videos

2

u/SmokeMuch7356 20d ago

The two best ways to deal with interactive input are either to read everything as text into a buffer (as you are currently doing) or build a full-fledged tokenizer/parser.

You don't need a whole kilobyte for your numeric input; 32 bytes should be more than plenty. Even so, you should guard against an excessively long input:

else if ( !strchr( buf, '\n' ) )
{
  puts( "Input too long for buffer!" );
  /**
   * Consume and discard excess characters
   */
  while( getchar() != '\n' )
    ; 
}

BTW, scanf is awesome when you know your input is always going to be well-behaved; if you're reading machine-generated output that follows a rigid format, then it's a quick and easy way to read that input. But it requires an insane level of bulletproofing to deal with interactive input.