You should be using unsigned char to manage characters. For example, fgetc returns a “character as an unsigned char converted to an int” (C 2018 7.21.7.1). Use char can result in negative values and undefined behavior, as explained below.
7.4 1 defines the behavior of the <ctype.h> functions only for arguments whose value is representable as an unsigned char or EOF:
In all cases the argument is an int, the value of which shall be representable as an unsigned char or shall equal the value of the macro EOF. If the argument has any other value, the behavior is undefined.
Thus, if you have a char with a negative value, and you pass it to one of the <ctype.h> functions, that value is not representable as an unsigned char. And it is generally not EOF either. The negative char value will be implicitly converted to an int by the function call, but the value will remain negative. So the behavior would not be defined by the C standard.
Per 6.2.5 3, all members of the basic execution character set have non-negative values:
If a member of the basic execution character set is stored in a char object, its value is guaranteed to be nonnegative.
Per 5.2.1 3, The basic execution character set includes at least the Latin alphabet in uppercase and lowercase, the ten digits, space, horizontal tab, vertical tab, form feed, alert, backspace, carriage return, new line, and these characters:
!"#%&’()*+,-./: ;?[\]^_{|}~
So, if your string has any other character, it could have a negative value. Then, the behavior of the <ctype.h> functions is not defined by the C standard.