1

I made a short code like below.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
 
int32_t main(int32_t argc, int8_t *argv[])
{
   int32_t i;
 
   if (argc < 1)
   {
      printf("Error\n");
   }
 
   for (i = 0 ; i < argc ; i++)
   {
      printf("argv[%d] = %s\n", i, argv[i]);
   }
 
   return 0;
}

And compiled like below, then I see a warning like below.

$ gcc -W -Wall main.c
main.c:6:9: warning: second argument of ‘main’ should be ‘char **’ [-Wmain]
 int32_t main(int32_t argc, int8_t *argv[])

What is the best practice to use int8_t?

Cprogrammer
  • 153
  • 9
  • 2
    `char` and `signed char` (what `int8_t` is probably typedef'd as) are distinct types, even if `char` is signed. See https://stackoverflow.com/questions/2054939/is-char-signed-or-unsigned-by-default – Kevin Dec 21 '21 at 15:36

4 Answers4

3

At its root I believe you're asking a style question, which means you're unlikely to get a definitive answer. Opinions on style are, well, opinions.

Some people believe that you should use C's "natural" types — char, short, int, long, and their unsigned variants — most of the time, and that you should use exact-size types like int32_t only when you absolutely have to.

Some people believe that the variability implied by the "natural" types is an unrelenting source of bugs, and they believe that you should use exact-size types always.

Now, with that said, the specific case of writing

int32_t main(int32_t argc, int8_t *argv[])

is objectively wrong, for at least three reasons:

  1. On a platform where type int is 16 bits, this wrongly declares main's return type, and the type of the argc argument, as a 32-bit type.
  2. On a platform where type char is unsigned, this wrongly declares argv as an array of signed character pointers. That's probably what gcc was complaining about for you.
  3. On a more philosophical level, main is not a function whose function signature you get to pick. Somebody else declared main, somebody else is calling main, your job is only to provide a definition for main. So you simply have to use the types somebody else specified, even if your rule is that you want to use exact-size types whenever you can. Here, you can't.

Bottom line: Please use one of these two (equivalent) forms for main with arguments:

int main(int argc, char *argv[])

int main(int argc, char **argv)

Unless you're writing "freestanding" code, anything else is confusing, misleading, nonstandard, or wrong. (It's also acceptable to define a main that takes no arguments.)

What is the best practice to use int8_t?

I would say, when you really, really need a tiny, 8-bit, signed integer, or perhaps when you're manipulating memory as signed bytes. But I would not go using int8_t instead of char everywhere, because it's going to cause you lots of problems, and it's not going to buy you anything.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • This is **not** a style question. More precisely: `int8_t` is likely an alias for `signed char` which is a different type from `char` even if `char` is signed by default. – chqrlie Dec 21 '21 at 15:39
  • The standard contains the text "...or equivalent" which is clarified by a foot note "Thus, int can be replaced by a typedef name defined as int, or the type of argv can be written as char ** argv, and so on." – Lundin Dec 21 '21 at 15:40
  • @chqrlie The question's title is a style question. The specific example concerning `main` is not. – Steve Summit Dec 21 '21 at 15:42
  • I agree to this is style question. Actually, I like the style to use exact size type instead of natural/traditional ones. But as mentioned earlier, I cannot use it int8_t for main(). And that was bothering me even though main() is only one case I have a problem. So, it means the best practice is accept main() as an exceptional case. It seems. – Cprogrammer Dec 21 '21 at 15:59
  • @Cprogrammer: I would argue that best practice is to use standard prototypes unchanged for `main` and all library functions. It is also advisable to use `char` for actual characters used for text as opposed to `int8_t` and `uint8_t` for signed and unsigned bytes read from binary contents. String literals should be considered `const` and manipulated via `const char *`. Code should behave in a defined manner regardless of the signedness of the `char` type. This is not just a question of style, it is a sane habit to improve readability and sturdiness, avoid confusion and some mistakes. – chqrlie Dec 21 '21 at 17:16
1

What you have posted is an implementation-defined form of main(). It is only allowed in two cases:

  • Either it is 100% compatible with int main (int argc, char** argv), or
  • It is an implementation-defined form that the compiler documentation have told you is fine to use.

It is the C standard and the compiler which decide the acceptable forms of main(), never the programmer.

Notably int8_t may or may not be compatible with char, since char has implementation-defined signedness.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Even if `char` is signed by default, `signed char` and `char` are different types. – chqrlie Dec 21 '21 at 15:41
  • 1
    @chqrlie Yes but the are compatible types if they have the same signedness. "The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char." – Lundin Dec 21 '21 at 15:44
  • @Lundin But being different types, a `char**` is incompatible with a `signed char**`. And regardless, them being different types means the `main` function definition isn't a valid standard one. – Kevin Dec 21 '21 at 15:46
  • @Kevin `main()` can have various signatures `int main(void)`, `int main(int argc, char *argv[])`, the equivalent or _some other implementation-defined manner_. `signed char **argv` is not certainly invalid, but may be. – chux - Reinstate Monica Dec 21 '21 at 16:37
  • @chux-ReinstateMonica Fair enough. I didn't realize that implementations were allowed to provide other valid signatures for main: [`/* another implementation-defined signature */ `(since C99)](https://en.cppreference.com/w/c/language/main_function) – Kevin Dec 21 '21 at 16:43
  • @Kevin: the classic implementation-defined alternative signature is `int main(int argc, char *argv[], char *envp[])` – chqrlie Dec 21 '21 at 17:08
1

What is the best practice to use int8_t?

When you need a very small signed integer. That's not the same as char. char comes in three flavours:

signed char
unsigned char
char

If you know you want a signed int8_t, use it. If you are dealing with standard string API:s, like main, use char.

int8_t doesn't even need to exist. It's implementation specific. There are platforms where it doesn't exist.

The same goes for int32_t that you use instead of int. It doesn't necessarily exist and even if it does, it's not always a typedef for int - so use int if you want to stay portable.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
1

Your approach has many drawbacks:

  • the prototype for main() is not compatible with any of the standard ones, even if int has 32 bits and if char is signed because char and signed char are different yet compatible types but char * and signed char * are incompatible types.

  • if int is not the same as int32_t, the prototype is definitely incompatible with the standard one and the behavior is undefined.

  • printf("argv[%d] = %s\n", i, argv[i]); would have undefined behavior if type int is not exactly the same as int. You could use the macros from <inttypes.h>, but they are rather cumbersome and would be the code less readable.

Hence the prototype for your main function should be int main(int argc, char *argv[]) and for consistency, i should be defined with the same type as argc: int.

The resulting code is quite simple and readable:

#include <stdio.h>
 
int main(int argc, char *argv[]) {
    int i;
 
    if (argc < 1) {
        printf("Error\n");
    }
 
    for (i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
 
    return 0;
}

I would argue that best practice regarding int8_t vs char is to use standard prototypes unchanged for main and all library functions. It is also advisable to use char for actual characters used for text as opposed to int8_t and uint8_t for signed and unsigned bytes read from binary contents. String literals should be considered const and manipulated via const char *. Code should behave in a defined manner regardless of the signedness of the char type. This is not just a question of style, it is a sane habit to improve code readability and sturdiness, avoid confusion and some mistakes.

chqrlie
  • 131,814
  • 10
  • 121
  • 189