I've been studying more C using C Programming: A Modern Approach: A Modern Approach 2nd Edition by K.N. King as well as reading the comp.lang.c FAQ and the Code Review section here on Stack Exchange to improve my C skills. I would like your feedback on this recent program I wrote to calculate the volume of a box. It uses ncurses, currently just featuring text input/output but I am planning on expanding on that as I learn more about how to use ncurses.
Specifically, I would like to know thoughts on the following:
I am currently using an approach of
getnstr(*str, n)
→ temporary buffer → conversion touint64_t
. I have implemented this to prevent buffer overflow attacks. Is my approach a good way to check this? From what I've read, I saw thatscanf()
family of functions isn't the best choice for scanning input, so I have used advice I previously received here to implement a solution.Is my approach to "error handling" a good one? Besides gracefully asking again for input upon failure (I know that would be a better approach), is this errno assignment a good practice, or is there a preferred way to do this in C? I saw a few examples online when I searched this, most people used something similar yet others used raise and some used
longjmp
/setjmp
. I know there may be multiple right ways of doing this, but what would be preferred?my_strto64
is implemented for conversion touint64_t
. Is this function safe? Can it be improved in any way? This function is a slightly modified version of one I received as feedback from previous code which I had received as an answer to a previous Code Review on here.Currently I'm just checking the return value of
sprintf
- I would like to have some way to indicate to the user if the supplied values are not outputting the correct result due to the handling of overflow during multiplication. I know this is quite a simple program as it is, but this is something I was thinking about and I did think of checking the length of each string but couldn't think of a clean way to check proactively on this scenario as the lengths of individual strings aren't enough to ensure the right result after multiplication. It is something I definitely want to learn. I would appreciate any advice on this. I was thinking maybe something like dividing the maximum possibleuint64_t
value by one of the numbers and then multiplying the other two numbers and comparing results to see the possiblity of overflow.Should
calloc
/malloc
be used, or a stack-based allocation such asmalloca
? I have seen some people say it's better to usemalloca
or an array-based creation for strings on the stack instead of allocating memory on the heap, due to stack memory automatically freeing at the end of a function. Which is a better choice in this scenario?
Would appreciate your feedback on the points above and/or anything else you find that can be improved in my code. I always learn a lot from all of you knowledgeable folks here.
#include <stdlib.h>
#include <ncurses.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#define DECSTR_UINT64_MAX_LENGTH 20
int my_strto64(uint64_t *dest, const char *input_str);
int main(void) {
char tmp_str[DECSTR_UINT64_MAX_LENGTH + 1]; // for input prior to parsing (+1 for NUL)
uint64_t height, width, length;
// length + 1 (NUL character)
char* total_string = calloc(DECSTR_UINT64_MAX_LENGTH + 1, 1);
if (!total_string) {
errno = ENOMEM;
perror("Unable to allocate memory");
return errno;
}
initscr();
printw("--- Volume Calculator --\n");
printw("Enter length: ");
getnstr(tmp_str, DECSTR_UINT64_MAX_LENGTH);
if(!my_strto64(&length, tmp_str)) {
errno = EIO;
perror("Unable to scan length");
return errno;
}
printw("Enter width: ");
getnstr(tmp_str, DECSTR_UINT64_MAX_LENGTH);
if(!my_strto64(&width, tmp_str)) {
errno = EIO;
perror("Unable to scan length");
return errno;
}
printw("Enter height: ");
getnstr(tmp_str, DECSTR_UINT64_MAX_LENGTH);
if(!my_strto64(&height, tmp_str)) {
errno = EIO;
perror("Unable to scan length");
return errno;
}
int return_value = sprintf(total_string, "Total: %" PRIu64, height * length * width);
// sprintf returns a negative value if it fails, so check it
if (return_value < 0) {
errno = EIO;
perror("Cannot multiply height * length * width");
return errno;
}
printw(total_string);
free(total_string);
refresh();
getch();
endwin();
return 0;
}
/**
* Converts input_str to uint64_t -> returns 0 on success
* Adapted from: https://codereview.stackexchange.com/a/206773/78786
*/
int my_strto64(uint64_t *dest, const char *input_str) {
char *endptr;
errno = 0;
unsigned long long parsed_long_long = strtoull(input_str, &endptr, 10);
#if ULLONG_MAX > UINT64_MAX
if (y > UINT64_MAX) {
uint64_t *dest = UINT64_MAX;
errno = ERANGE;
return errno;
}
#endif
*dest = (uint64_t) parsed_long_long;
if (errno == ERANGE) {
return errno;
}
// `strtou...()` function wraps with `-`
// lets return an error if its negative
if (*dest && strchr(input_str, '-')) {
*dest = 0;
errno = ERANGE;
return errno; // negative, we don't want it
}
if (input_str == endptr) {
errno = EDOM;
return errno; // unsuccessful at converting, still *char
}
while (isspace((unsigned char) *endptr)) endptr++;
if (*endptr) {
errno = EIO;
return errno; // contains invalid characters
}
return (int) parsed_long_long;
}