DNH
DNH

Reputation: 29

Convert hex to binary from command line arguments C

This is a homework assignment where I have to convert hex to binary when a command is called in the terminal. My "teacher" isn't really "teaching" C so I'm dead lost. I have to include a procedure, void printBits(unsigned long i), that prints the bits in i. It will be invoked from the command line using the '-p' switch followed by a 32-bit unsigned long integer in hex form. EX: $ lab3 -p 0x5

Output: 0000 0000 0000 0000 0000 0000 0000 0101

Please don't just give me the code. I need to understand this.

void printBits(unsigned long number) {
    // Declare Array to hold our binary number
    char binaryNumber[32];
    // For Loop Index
    int i;
    for (i = 31; i >= 0; --i)
    {
    binaryNumber[i] = (number & 1);
    number >>= 1;
    }

Upvotes: 0

Views: 2545

Answers (4)

David C. Rankin
David C. Rankin

Reputation: 84521

There are a number of ways to approach printing the binary representation for any number. First, you can simply output the result of your shift and index operation directly (to stdout, a file, etc...) This seems to be the approach you began with, but then you declared a 32 bit buffer. While you can certainly do that, there is no need to buffer the results if you are not going to return a pointer to the completed buffer. (that brings me to my 3rd point, below)

Simply outputting bits without storing/returning a pointer to the bits in a nul-terminated string, has its place but is generally of limited use. Nevertheless, it is a common problem that encompasses the basics of all approaches. Creating an unpadded binary representation can be approached as follows:

/** unpadded binary representation of 'v'. */
void binprn (const unsigned long v)
{
    if (!v)  { putchar ('0'); return; };  /* if v = 0 output '0' */

    size_t sz = sizeof v * CHAR_BIT;  /* get the number of bits in v */
    unsigned long rem = 0;        /* variable to hold shifted result */

    while (sz--)               /* for each bit (in decreasing order) */
        if ((rem = v >> sz))   /* if bits exist in the shifted value */
            putchar ((rem & 1) ? '1' : '0');    /* output '1' or '0' */
}

The comments are fairly explanatory. The scheme is to shift each bit beginning with the most significant bit (e.g. bit 31 (31-0) for a 32-bit number. You check whether there are any 1-bits following the shift (if not, the shift exceeds the most significant bit position in the number and nothing need be printed). Once there is a bit found in rem, there will always be bits bits to print throughout the remainder of the loop iterations because you are shifting by a decreasing amount. By starting with the most significant bit (which prints first), you end up with your bits printed out in the correct order and only printing the number of bits that make up the number.

Generally when you are simply outputting the binary representation directly to the screen, you will only want to output bits up to the most significant bit. (which prevents outputting a 1 with 63 0's in front of it making a mess out of things.)

Next, is outputting a padded binary representation to some number of bits. This is useful if you just want to look at the lower 8, 16, 32, ... bits in any number, but want a representation with a fixed number of bits each time. Here you simply pass the number of bits you wish to look at. Your function will then loop over that number of bit-positions in your number and output the results:

/** binary representation of 'v' padded to 'sz' bits.
 *  the padding amount is limited to the number of
 *  bits in 'v'. valid range: 0 - sizeof v * CHAR_BIT.
 */
void binprnpad (const unsigned long v, size_t sz)
{
    if (!sz) putchar ((v & 1) ? '1' : '0');  /* if no sz, '0' is fine */

    if (sz > sizeof v * CHAR_BIT)  /* if sz exceed # of bits, limit   */
        sz = sizeof v * CHAR_BIT;

    while (sz--)  /* for sz positions in decreasing order, '1' or '0' */
        putchar ((v >> sz & 1) ? '1' : '0');
}

You will notice the primary difference here is that you do not have to concern yourself with checking whether bits remain to prevent printing unwanted leading zeros, because you are controlling the number of bits with the parameter sz. (it's up to you what you do if a 0 size is passed, I just choose to output '0')

Now for the third point mentioned above. Simply outputting bits is cumbersome from a formatting standpoint back in the main body of your code. I find it far more useful to store the bits in a character array (nul-terminated so it can be treated as a string) and return a pointer to the array so it can be passed to printf, etc. Now you either have to pass an adequately sized array as a parameter, declare a static array so the array isn't destroyed on function return, or dynamically allocate storage for the array within the function. All have pluses and minuses that you have to weigh depending upon the needs of your code. e.g.:

/** returns pointer to binary representation of 'v' zero padded to 'sz'.
 *  returns pointer to string contianing binary representation of
 *  unsigned 64-bit (or less ) value zero padded to 'sz' digits.
 */
char *binpad (const unsigned long v, const size_t sz)
{
    static char s[BITS_PER_LONG + 1] = {0};
    char *p = s + BITS_PER_LONG;
    register size_t i;

    for (i = 0; i < sz; i++)
        *--p = (v>>i & 1) ? '1' : '0';

    return p;
}

The code functions the same as its non-buffered padded counterpart above. Note how p returns the beginning position within the buffer where sz number of bits begins. Also note that you will need a constant for BITS_PER_LONG denoting the number of bits in a long on your hardware. (which is normally handled in a manner similar to BUILD_64)

note: just be aware that one limitation to a static declaration is the conversion function can only be used once in any one printf call (or within any single line of code) since there is only one storage array for the binary conversion. (You can always make any number of calls and store the results in different locations just before the making the printf call)

One final variation on the binary print is the print the representation with separators included to make it easier to identify and compare between binary strings (especially when dealing with longer sequences of 0's and 1's. e.g.:

hexval : 0xdeadbeef  =>  11011110-10101101-10111110-11101111

The function essentially works the same as binpad above, but with the addition that the static buffer is larger to accommodate the separators and an additional check on bit-position to determine when a separator should be added to the buffer:

/** returns pointer to formatted binary representation of 'v' zero padded to 'sz'.
 *  returns pointer to string contianing formatted binary representation of
 *  unsigned 64-bit (or less ) value zero padded to 'sz' digits with char
 *  'sep' placed every 'szs' digits. (e.g. 10001010 -> 1000-1010).
 */
char *binfmt (const unsigned long v, const unsigned char sz, 
            const unsigned char szs, const char sep)
{
    static char s[BITS_PER_LONG * 2 + 1] = {0};
    char *p = s + 2 * BITS_PER_LONG;
    register size_t i;

    *p = 0;
    for (i = 0; i < sz; i++) {
        p--;
        if (i > 0 && szs > 0 && i % szs == 0)
            *p-- = sep;
        *p = (v >> i & 1) ? '1' : '0';
    }

    return p;
}

The remainder of your problem is simply to process the command line arguments, and perform the conversion from input string to unsigned value along with the validation checks that the number does not exceed 32-bits, etc.. You can either process the arguments with getops or for a small number of simple options you can simply use a loop. On Linux, the only required arguments your code should respond to are -h for help and -v for version. While nobody does this for short examples, etc., it is at least nice to have that information. Look over the following example that puts all the pieces together and let me know if you have any questions:

#include <stdio.h>
#include <stdlib.h> /* for strtoul */
#include <errno.h>  /* for errno   */
#include <limits.h> /* for UINT_MAX, ULONG_MAX, CHAR_BIT */

#define PACKAGE "hex2bin"
#define VERSION "0.01"

/* BUILD_64 - Check x86/x86_64 */
#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

/* BITS_PER_LONG */
#ifdef BUILD_64
# define BITS_PER_LONG 64
#else
# define BITS_PER_LONG 32
#endif

unsigned long processopts (int argc, char **argv);
unsigned long xstrtoul (char *s);
void binprn (const unsigned long v);
void binprnpad (const unsigned long v, size_t sz);
char *binpad (const unsigned long v, const size_t sz);
char *binfmt (const unsigned long v, const unsigned char sz, 
            const unsigned char szs, const char sep);
void help (int xcode);

int main (int argc, char **argv) {

    unsigned long hexval = processopts (argc, argv);

    /* print unpadded binary */
    printf ("\n hexval : 0x%lx (%lu)  =>  ", hexval, hexval);
    binprn (hexval);
    printf ("\n");

    /* print padded to 32-bits */
    printf ("\n hexval : 0x%lx (%lu)  =>  ", hexval, hexval);
    binprnpad (hexval, sizeof (int) * CHAR_BIT);
    printf ("\n");

    /* padded binary returned as formatted string
     * with '-' separators every 8 bits
     */
    printf ("\n hexval : 0x%lx (%lu)  =>  %s\n\n", hexval, hexval,
            binfmt (hexval, sizeof (int) * CHAR_BIT, CHAR_BIT, '-'));

    return 0;
}

/* quick custom argument handler */
unsigned long processopts (int argc, char **argv)
{
    size_t i = 1;
    unsigned long val = 0;

    if (argc < 2) help (0);         /* insufficient arguments       */

    for (; argv[i]; i++) {          /* for each argument            */
        if (*argv[i] == '-') {      /* for each beginning with '-'  */
            switch (argv[i][1]) {
                case 'h':           /* respond to '-h' help         */
                    help (0);
                case 'p':           /* handle '-p' convert value    */
                    if (!argv[i+1]) {   /* if '-p' w/o next arg     */
                        fprintf (stderr, "error: insufficient input.\n");
                        help (1);
                    }
                    if (*argv[i+1] != '0' || /* validate hex input  */
                        (argv[i+1][1] != 'x' && argv[i+1][1] != 'X')) {
                        fprintf (stderr, "error: invalid 'hex_value' input.\n");
                        help (1);                    
                    }
                    val = xstrtoul (argv[i+1]); /* convert to ulong */
                    if (val > UINT_MAX) {       /* validate 32-bits */
                        fprintf (stderr, "error: input value exceeds 32-bits.\n");
                        help (1);
                    }
                    break;
                case 'v':           /* respond to '-v' version      */
                    printf ("%s, version %s\n", PACKAGE, VERSION);
                    exit (0);
                default :
                    fprintf (stderr, "error: invalid/unrecognized option '%s'.\n",
                            argv[i]);
                    help (1);
            }
        }
    }
    return val; /* return val */
}

unsigned long xstrtoul (char *s)
{
    unsigned long v = 0;
    errno = 0;

    /* test for hex or decimal conversion */
    if (*s == '0' && (s[1] == 'x' || s[1] == 'X'))
        v = strtoul (s, NULL, 16);
    else
        v = strtoul (s, NULL, 10);

    /* check for various possible errors */
    if ((errno == ERANGE && v == ULONG_MAX) || (errno != 0 && v == 0)) {
        perror ("strtoul");
        exit (EXIT_FAILURE);
    }
    return v;
}

/** unpadded binary representation of 'v'. */
void binprn (const unsigned long v)
{
    if (!v)  { putchar ('0'); return; };

    size_t sz = sizeof v * CHAR_BIT;
    unsigned long rem = 0;

    while (sz--)
        if ((rem = v >> sz))
            putchar ((rem & 1) ? '1' : '0');
}

/** binary representation of 'v' padded to 'sz' bits.
 *  the padding amount is limited to the number of
 *  bits in 'v'. valid range: 0 - sizeof v * CHAR_BIT.
 */
void binprnpad (const unsigned long v, size_t sz)
{
    if (!sz) putchar ((v & 1) ? '1' : '0');

    if (sz > sizeof v * CHAR_BIT)
        sz = sizeof v * CHAR_BIT;

    while (sz--)
        putchar ((v >> sz & 1) ? '1' : '0');
}

/** returns pointer to binary representation of 'v' zero padded to 'sz'.
 *  returns pointer to string contianing binary representation of
 *  unsigned 64-bit (or less ) value zero padded to 'sz' digits.
 */
char *binpad (const unsigned long v, const size_t sz)
{
    static char s[BITS_PER_LONG + 1] = {0};
    char *p = s + BITS_PER_LONG;
    register size_t i;

    for (i = 0; i < sz; i++)
        *--p = (v>>i & 1) ? '1' : '0';

    return p;
}

/** returns pointer to formatted binary representation of 'v' zero padded to 'sz'.
 *  returns pointer to string contianing formatted binary representation of
 *  unsigned 64-bit (or less ) value zero padded to 'sz' digits with char
 *  'sep' placed every 'szs' digits. (e.g. 10001010 -> 1000-1010).
 */
char *binfmt (const unsigned long v, const unsigned char sz, 
            const unsigned char szs, const char sep)
{
    static char s[BITS_PER_LONG * 2 + 1] = {0};
    char *p = s + 2 * BITS_PER_LONG;
    register size_t i;

    *p = 0;
    for (i = 0; i < sz; i++) {
        p--;
        if (i > 0 && szs > 0 && i % szs == 0)
            *p-- = sep;
        *p = (v >> i & 1) ? '1' : '0';
    }

    return p;
}

void help (int xcode)
{
    xcode = xcode ? xcode : 0;  /* set default exit code */

    printf ("\n %s, version %s\n\n"
            "  usage:  %s -p hex_value (32-bit)\n\n"
            "  converts 'hex_value' to its binary representation.\n\n"
            "    Options:\n\n"
            "      -h            this help.\n"
            "      -p hex_value  display binary representation of 'hex_value'.\n"
            "      -v            display version information.\n\n",
            PACKAGE, VERSION, PACKAGE);

    exit (xcode);
}

Use/Output

$ ./bin/hex2bin -p 0xe7

 hexval : 0xe7 (231)  =>  11100111

 hexval : 0xe7 (231)  =>  00000000000000000000000011100111

 hexval : 0xe7 (231)  =>  00000000-00000000-00000000-11100111


$ ./bin/hex2bin -p 0xdeadbeef

 hexval : 0xdeadbeef (3735928559)  =>  11011110101011011011111011101111

 hexval : 0xdeadbeef (3735928559)  =>  11011110101011011011111011101111

 hexval : 0xdeadbeef (3735928559)  =>  11011110-10101101-10111110-11101111


$ ./bin/hex2bin -h

 hex2bin, version 0.01

  usage:  hex2bin -p hex_value (32-bit)

  converts 'hex_value' to its binary representation.

    Options:

      -h            this help.
      -p hex_value  display binary representation of 'hex_value'.
      -v            display version information.


$ ./bin/hex2bin -v
hex2bin, version 0.01

Upvotes: 1

user5069935
user5069935

Reputation:

Here's one way your program could look. I've commented the important bits, but you must look at the documentation for the functions that are being used. If you are using linux (looks like you are judging by the original question) then you can use the linux "manual pages" such as man sscanf to give full info about sscanf or any other function in the C library.

Compile it with: gcc main.c -o lab3

/* main.c */
#include <stdio.h> //provides putchar()
#include <strings.h> //provides sscanf() and strcmp()
#include <stdlib.h> //provides EXIT_x values

void printBits(unsigned long i)
{
  int j; //define a loop counter

  for(j = 0 ; j < 32 ; j++)
  {
    //test the highest bit and write a 1 or a 0
    //(we always test the highest bit but we shift the number along each time)
    putchar(i & 0x80000000 ? '1' : '0');

    //shift the bits along by one ready for the next loop iteration
    i <<= 1;

    //print a space after every 4th bit to break it up into nybbles
    if((j % 4) == 3)
      putchar(' ');
  }

  //finish the output in a tidy manner by writin a newline
  putchar('\n');
}


//a helpful function to assist the user
void usage(const char* progname)
{
  //show the user the proper way to run the program
  printf("%s -p 0x1234abcd\n", progname);
}

//this version of the main() signature gives access to commandline arguments
int main(int argc, char** argv)
{
  //flag to show the commandline arguments are in the wrong format
  int badargs = 0;

  //variable to store the 32 bit number given by the user
  unsigned long value;

  if(argc == 3) //do we have the right number of commandline arguments?
    if(strcmp(argv[1], "-p") == 0) //is argv[1] equal to "-p" ?
      if(sscanf(argv[2], "0x%x", &value) == 1) //is the number formatted like hex?
        printBits(value); //success, user input was good, print the bits!
      else
        badargs = 1; //the integer was not properly formatted as hexadecimal like 0x1234
    else
      badargs = 1; //argv[1] was not "-p"
  else
    badargs = 1; //wrong number of args given by user

  if(badargs) //we detected bad argument syntax earlier so we'd better remind the user what to do
  {
    printf("Incorrect argument syntax\n\n\t");
    usage(argv[0]); //argv[0] is the name of your executable program file ("lab3" in your case)
    putchar('\n');
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

I wrote this one myself but there are many examples of this kind of "teaching exercise" on the web so I don't think it's much of a spoiler to just include the verbatim code here.

Upvotes: 0

AndersK
AndersK

Reputation: 36082

First you convert the string that you get from the command line to an integer, simplest is to use sscanf

e.g.

if ( sscanf( argv[1], "%X", &n ) == 1)
{
  ...

now you have the decimal value n.

In order to convert to binary you need to go through each bit in the unsigned integer.

By doing bitwise-and with the decimal value you can check each individual bit whether it is set and print either a '1' or '0' depending on the bit

for (int i = 0; i < 32; ++i)
{
   unsigned int mask = 0x8000 >> i; // this bit we check
   char ch = (n & mask) ? '1' : '0'; // see if it is set or not
   ...
}

Upvotes: 0

4pie0
4pie0

Reputation: 29724

Each umber is stored in machine as a series of bits. To print them to console you need to pass a string in which each character is 1 (0x31) or 0 (0x30) depending on whether corresponding bit in number is set. And last character MUST be '\0' to signify end of string.

After you prepared buffer of characters, printing them to console can be done using e.g. fprintf:

fprintf(stdout, "%s\n", binaryNumber); // (maybe use stderr)

with a little correction of your code to assure string is NULL terminated (it has '\0' as last character in buffer) and you put characters into buffer (otherwise 1 and 0 which are not printable characters are put into buffer and you will see no output at all):

void
hex_to_bin_print(unsigned long number)
{
    char binaryNumber[33];
    int i;
    for (i = 31; i >= 0; --i)
    {
        binaryNumber[i] = (number & 1) ? '1' : '0';
        number >>= 1;
    }
    binaryNumber[32] = '\0';
    fprintf(stdout, "Number %s\n", binaryNumber);
}

int main(void) {
    hex_to_bin_print(1);
    hex_to_bin_print(2);
    hex_to_bin_print(15);
    hex_to_bin_print(127);
    hex_to_bin_print(256);
    hex_to_bin_print(12345);
    return 0;
}

prints:

Number 00000000000000000000000000000001

Number 00000000000000000000000000000010

Number 00000000000000000000000000001111

Number 00000000000000000000000001111111

Number 00000000000000000000000100000000

Number 00000000000000000011000000111001

Upvotes: 0

Related Questions