13

I can do this:

int main(int argc, char** argv) {
  unsigned char cTest = 0xff;
  return 0;
}

But what's the right way to get a hexadecimal number into the program via the command line?

unsigned char cTest = argv[1];

doesn't do the trick. That produces a initialization makes integer from pointer without a cast warning.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Pieter
  • 31,619
  • 76
  • 167
  • 242
  • 4
    Command line arguments are **always** strings. Some platforms may make them Unicode, in which case a simple `strtol` will not work. – dirkgently Jan 29 '10 at 15:20
  • @dirkgently: in which case `mbstol` or whatever the multibyte or wide char (or TCHAR) variants would cover that? (I also doubt this is a key concern given the nature of the question – Ruben Bartelink Jan 29 '10 at 15:31
  • 1
    @dirkgently: in that case, is the second parameter of `main()` not of type `char **`? That can't be standards-compliant. – Alok Singhal Jan 29 '10 at 15:33
  • @Alok: They could still be multi-byte in which case mbstoul would be best, but that's far too fancy in the context of the question methinks – Ruben Bartelink Jan 29 '10 at 15:37

6 Answers6

18

I think some people arriving here might just be looking for:

$ ./prog `python -c 'print "\x41\x42\x43"'`
$ ./prog `perl -e 'print "\x41\x42\x43"'`
$ ./prog `ruby -e 'print "\x41\x42\x43"'`
Peter Le Bek
  • 982
  • 8
  • 16
12

As the type of main indicates, arguments from the command line are strings and will require conversion to different representations.

Converting a single hexadecimal command-line argument to decimal looks like

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

int main(int argc, char *argv[])
{
  printf("%ld\n", strtol(argv[1], NULL, 16));

  return 0;
}

Example usage:

$ ./hex ff
255

Using strtol and changing the final argument from 16 to 0 as in

printf("%ld\n", strtol(argv[1], NULL, 0));

makes the program accept decimal, hexadecimal (indicated by leading 0x, and octal (indicated by leading 0) values:

$ ./num 0x70
112
$ ./num 070
56
$ ./num 70
70

Using the bash command shell, take advantage of ANSI-C Quoting to have the shell perform the conversion, and then your program just prints the values from the command line.

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  int i;
  for (i = 1; i < argc; i++) {
    unsigned char value = argv[i][0];
    if (strlen(argv[i]) > 1)
      fprintf(stderr, "%s: '%s' is longer than one byte\n", argv[0], argv[i]);

    printf(i + 1 < argc ? "%u " : "%u\n", value);
  }

  return 0;
}

Bash supports many formats of the form $'...', but $'\xHH' appears to be the closest match to your question. For example:

$ ./print-byte $'\xFF' $'\x20' $'\x32'
255 32 50

Maybe you pack the values from the command line into a string and print it.

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

int main(int argc, char *argv[])
{
  int i;

  if (argc > 1) {
    char *s = malloc(argc);
    if (!s) {
      fprintf(stderr, "%s: malloc: %s\n", argv[0], strerror(errno));
      return 1;
    }

    for (i = 1; i < argc; i++)
      s[i - 1] = strtol(argv[i], NULL, 16) & 0xff;

    s[argc - 1] = '\0';
    printf("%s\n", s);
    free(s);
  }

  return 0;
}

In action:

$ ./pack-string 48 65 6c 6c 6f 21
Hello!

All of the above is reinventing wheels that bash and the operating system already provide for you.

$ echo $'\x48\x65\x6c\x6c\x6f\x21'
Hello!

The echo program prints its command-line arguments on the standard output, which you can think of as a for loop over the arguments and a printf for each.

If you have another program that performs the decoding for you, use Command Substitution that replaces a command surrounded by backticks or $() with its output. See examples below, which again use echo as a placeholder.

$ echo $(perl -e 'print "\x50\x65\x72\x6c"')
Perl
$ echo `python -c 'print "\x50\x79\x74\x68\x6f\x6e"'`
Python
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
7

You could use strtoul which would walk through the characters in the string and convert them, taking into account the radix (16 in this context) that you pass in:-

char *terminatedAt;
if (argc != 2)
    return 1;

unsigned long value = strtoul( argv[1], &terminatedAt, 16);

if (*terminatedAt != '\0')
    return 2;

if (value > UCHAR_MAX)
    return 3;

unsigned char byte = (unsigned char)value;
printf( "value entered was: %d", byte);

As covered in the other examples, there are shorter ways, but none of them allow you to cleanly error check the processing (what happens if someone passes FFF and you've only got an unsiged char to put it into?

e.g. with sscanf:

int val;
sscanf(argv[1], &val)
printf("%d\n", val); 
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • `UCHAR_MAX` may be greater than `LONG_MAX`, so you should use `strtoul()`, and `value > 255` should be `value > UCHAR_MAX`. Finally, `static_cast` is C++; here you don't need a cast because of implicit conversion rules. – Alok Singhal Jan 29 '10 at 15:30
  • Ta, I had a `cout` in it in the intial version :P I considered using u initially but thought it would complicate matters too much given that the questioner is mainly looking to understand a concept that isnt immediately obvious to them. – Ruben Bartelink Jan 29 '10 at 15:36
  • I agree that sometimes one has to make things not-entirely-pedantically-correct for beginners, but in this case I think the pedantry isn't too hard, even for a beginner. It will prevent many people from assuming that `UCHAR_MAX == 255` or `LONG_MAX > UCHAR_MAX`, etc. Thanks for the edit and a good answer! – Alok Singhal Jan 29 '10 at 15:39
  • @Alok: Always happy to be pedantic, thanks! I think the cast is good here as it explains what's happening to a reader and will avoid warnings about the shortening conversion shoudl they be switched on. I'll leave out mbs concerns (and wchar_t concerns as the question ruled them out). Over and out! – Ruben Bartelink Jan 29 '10 at 15:43
4
unsigned char cTest = argv[1];

is wrong, because argv[1] is of type char *. If argv[1] contains something like "0xff" and you want to assign the integer value corresponding to that to an unsigned char, the easiest way would be probably to use strtoul() to first convert it to an unsigned long, and then check to see if the converted value is less than or equal to UCHAR_MAX. If yes, you can just assign to cTest.

strtoul()'s third parameter is a base, which can be 0 to denote C-style number parsing (octal and hexadecimal literals are allowed). If you only want to allow base 16, pass that as the third argument to strtoul(). If you want to allow any base (so you can parse 0xff, 0377, 255, etc.), use 0.

UCHAR_MAX is defined in <limits.h>.

Alok Singhal
  • 93,253
  • 21
  • 125
  • 158
1

The traditional way to do this kind of thing in C is with scanf(). It's exactly the inverse of printf(), reading the given format out of the file (or terminal) and into the variables you list, rather than writing them into it.

In your case, you'd use sscanf as you've already got it in a string rather than a stream.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
T.E.D.
  • 44,016
  • 10
  • 73
  • 134
-3

atoi, atol, strtoi, strtol

all in stdlib.h

biegleux
  • 13,179
  • 11
  • 45
  • 52
Pod
  • 3,938
  • 2
  • 37
  • 45
  • 1
    All pointed out 3 mins before (the see also of the linked man page) but not helping the questioner understand why its necessary or giving a sample. When a post isnt adding, it's good to delete it to keep it DRY – Ruben Bartelink Jan 29 '10 at 15:26