Преобразовать шестнадцатеричный код в двоичный из аргументов командной строки C

Это домашнее задание, где я должен преобразовать шестнадцатеричный код в двоичный, когда команда вызывается в терминале. Мой «учитель» на самом деле не «преподает» C, поэтому я совершенно потерян. Я должен включить процедуру void printBits(unsigned long i), которая печатает биты в i. Он будет вызываться из командной строки с помощью ключа «-p», за которым следует 32-разрядное длинное целое число без знака в шестнадцатеричной форме. ПРИМЕР: $ lab3 -p 0x5

Вывод: 0000 0000 0000 0000 0000 0000 0000 0101

Пожалуйста, не давайте мне код. Мне нужно понять это.

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;
    }

person DNH    schedule 19.02.2016    source источник
comment
Пожалуйста, не давайте мне код. Мне нужно понять это. ХОРОШО.   -  person 4pie0    schedule 19.02.2016
comment
Что у вас есть до сих пор?   -  person    schedule 19.02.2016
comment
Отредактировано, чтобы показать, что у меня есть. Не знаю, куда идти оттуда   -  person DNH    schedule 19.02.2016
comment
Лучше всего предоставить целую программу (желательно ту, которая компилируется), чтобы мы могли наилучшим образом вам помочь. Покажите нам все, а не только функцию printBits(), потому что вам нужно решить несколько проблем, а не только манипуляции с бинарными файлами. Вы также должны взять аргументы командной строки, проанализировать их на правильность, а затем преобразовать в целое число. Ваш учитель спрашивает чертовски много от вас, если он / она не желает вам помочь.   -  person    schedule 19.02.2016
comment
Вот подсказка, обратите внимание на следующие функции в библиотеке C: putchar(), strcmp(), sscanf(). Это позволит вам решить все проблемы, поставленные перед вами учителем. Наконец, чтобы иметь дело с аргументами командной строки, вам нужно будет использовать параметры argc и argv в вашей функции main(), они широко используются и могут быть легко найдены в Google.   -  person    schedule 19.02.2016
comment
Мне жаль, что я даже не начал ничего другого. Я просто пытался понять, как сделать функцию printBits, и Google не слишком помог. Я просто могу искать не в тех местах, хотя   -  person DNH    schedule 19.02.2016
comment
И учитель упрямо не преподает C. Назовите меня плаксивым учеником, но буквально все, что я знаю о C, из Интернета и того незначительного количества Java, которое я выучил.   -  person DNH    schedule 19.02.2016
comment
Эта проблема не слишком сложна. Что вам нужно понять, так это то, что когда вы сдвигаете число, зацикливая 0-size, вы получаете биты в порядке хост-байтов, поэтому первые биты, которые вы сдвигаете, являются наименее значимыми битами на оборудовании с прямым порядком байтов. Вы можете справиться с этим несколькими способами, проиндексировать позиции символов в вашем массиве от высокого к низкому, чтобы исправить проблему, или вы можете сначала выполнить большие сдвиги (например, n >> size - 1) и записать биты в нормальном порядке. Как только вы разберетесь с этой причудой, кодирование станет легким.   -  person David C. Rankin    schedule 19.02.2016
comment
@DNH, у тебя есть полное право жаловаться, если твой учитель не выполняет свою работу. Я бы хотел, чтобы у меня был интернет, когда я учился в школе. Переполнение стека — это хорошее место для быстрого обучения, и здорово, что вы действительно хотите изучать программирование. Я самоучка в C и долгое время был профессиональным программистом, так что это вполне достижимо.   -  person    schedule 19.02.2016
comment
@DavidC.Rankin, порядок байтов хоста не имеет значения, если вы используете тип данных unsigned int для хранения значения. Простое смещение в правильном направлении и проверка правильного бита — это все, что имеет значение.   -  person    schedule 19.02.2016
comment
Вы правы, я думал быстрее, чем должен был. Намерение состояло в том, чтобы объяснить, почему извлечение битов с более низкими сдвигами приводит к более высоким битовым позициям в двоичном представлении.   -  person David C. Rankin    schedule 19.02.2016


Ответы (4)


Существует несколько способов печати двоичного представления любого числа. Во-первых, вы можете просто вывести результат вашей операции сдвига и индексации напрямую (в stdout, файл и т. д.). Кажется, это подход, с которого вы начали, но затем вы объявили 32-битный буфер. Хотя вы, безусловно, можете это сделать, нет необходимости буферизовать результаты, если вы не собираетесь возвращать указатель на заполненный буфер. (это подводит меня к моему 3-му пункту ниже)

Простой вывод битов без сохранения/возврата указателя на биты в строке с нулевым завершением имеет место быть, но, как правило, имеет ограниченное применение. Тем не менее, это общая проблема, которая охватывает основы всех подходов. К созданию незаполненного двоичного представления можно подойти следующим образом:

/** 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' */
}

Комментарии достаточно поясняющие. Схема состоит в том, чтобы сдвигать каждый бит, начиная со старшего бита (например, бит 31 (31-0) для 32-битного числа. Вы проверяете, есть ли какие-либо 1-биты после сдвига (если нет, сдвиг превышает максимальное значение). значащая битовая позиция в числе, и ничего не нужно печатать). Как только бит будет найден в rem, всегда будут биты для печати на протяжении оставшихся итераций цикла, потому что вы выполняете сдвиг на уменьшающуюся величину. самый значащий бит (который печатается первым), вы в конечном итоге распечатываете свои биты в правильном порядке и печатаете только количество битов, составляющих число.

Как правило, когда вы просто выводите двоичное представление непосредственно на экран, вам нужно выводить только биты до самого старшего бита. (что предотвращает вывод 1 с 63 0 перед ним, что приводит к беспорядку.)

Затем выводится дополненное двоичное представление с некоторым количеством битов. Это полезно, если вы просто хотите посмотреть на младшие 8, 16, 32, ... битов в любом числе, но каждый раз хотите иметь представление с фиксированным количеством битов. Здесь вы просто передаете количество битов, которые хотите просмотреть. Затем ваша функция будет перебирать это количество битовых позиций в вашем числе и выводить результаты:

/** 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');
}

Вы заметите, что основная разница здесь заключается в том, что вам не нужно заботиться о том, чтобы проверить, остались ли биты, чтобы предотвратить печать нежелательных начальных нулей, потому что вы управляете количеством битов с помощью параметра sz. (вам решать, что делать, если передан размер 0, я просто выбираю вывод '0')

Теперь о третьем пункте, упомянутом выше. Простой вывод битов является громоздким с точки зрения форматирования в основной части вашего кода. Я считаю, что гораздо полезнее хранить биты в массиве символов (с нулевым завершением, чтобы его можно было рассматривать как строку) и возвращать указатель на массив, чтобы его можно было передать в printf и т. д. Теперь вам нужно либо передать массив соответствующего размера в качестве параметра, объявить массив static, чтобы массив не уничтожался при выходе из функции, либо динамически выделить память для массива внутри функции. У всех есть плюсы и минусы, которые вы должны взвесить в зависимости от потребностей вашего кода. например.:

/** 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;
}

Код функционирует так же, как его небуферизованный дополненный аналог выше. Обратите внимание, как p возвращает начальную позицию внутри буфера, где начинается sz количество битов. Также обратите внимание, что вам понадобится константа для BITS_PER_LONG, обозначающая количество битов в long на вашем оборудовании. (который обычно обрабатывается аналогично BUILD_64)

примечание: просто имейте в виду, что одно ограничение для объявления static заключается в том, что функция преобразования может использоваться только один раз в любом вызове printf (или в любой отдельной строке кода), поскольку существует только один массив хранения для бинарное преобразование. (Вы всегда можете сделать любое количество звонков и сохранить результаты в разных местах непосредственно перед выполнением printf вызова)

Последним вариантом двоичной печати является печать представления с включенными разделителями, чтобы упростить идентификацию и сравнение между двоичными строками (особенно при работе с более длинными последовательностями 0 и 1, например:

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

Функция по существу работает так же, как binpad выше, но с добавлением того, что статический буфер больше для размещения разделителей и дополнительной проверки битовой позиции, чтобы определить, когда разделитель должен быть добавлен в буфер:

/** 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;
}

Остальная часть вашей проблемы заключается в том, чтобы просто обработать аргументы командной строки и выполнить преобразование из входной строки в беззнаковое значение вместе с проверкой правильности того, что число не превышает 32 бита и т. д. Вы можете либо обработать аргументы с помощью getops или для небольшого количества простых опций вы можете просто использовать цикл. В Linux единственными обязательными аргументами, на которые должен отвечать ваш код, являются -h для справки и -v для версии. Хотя никто не делает этого для коротких примеров и т. Д., По крайней мере, приятно иметь эту информацию. Посмотрите на следующий пример, который объединяет все части, и дайте мне знать, если у вас есть какие-либо вопросы:

#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);
}

Использование/вывод

$ ./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
person David C. Rankin    schedule 19.02.2016

Каждое число хранится в машине в виде последовательности битов. Чтобы вывести их на консоль, вам нужно передать строку, в которой каждый символ равен 1 (0x31) или 0 (0x30) в зависимости от того, установлен ли соответствующий бит в числе. И последний символ ДОЛЖЕН быть '\0', чтобы обозначить конец строки.

После того, как вы подготовили буфер символов, их печать на консоли может быть выполнена, например, с помощью fprintf:

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

с небольшой коррекцией вашего кода, чтобы гарантировать, что строка завершена NULL (она имеет «\ 0» в качестве последнего символа в буфере), и вы помещаете символы в буфер (в противном случае 1 и 0, которые не являются печатаемыми символами, помещаются в буфер, и вы увидите выхода нет вообще):

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;
}

печатает:

Номер 00000000000000000000000000000001

Номер 000000000000000000000000000000010

Номер 00000000000000000000000000001111

Номер 00000000000000000000000001111111

Число 00000000000000000000000100000000

Номер 00000000000000000011000000111001

person 4pie0    schedule 19.02.2016
comment
какова цель? «1»: «0»; что это делает? - person DNH; 19.02.2016
comment
@DNH, в противном случае 1 и 0, которые не являются печатными символами, помещаются в буфер, и вы вообще не увидите вывода - вы пробовали? - person 4pie0; 19.02.2016
comment
?: — это сокращенный способ выполнения оператора if. состояние? если_истина : если_ложь. Если вы предпочитаете, вы можете просто использовать обычный оператор if/else. Многие люди предпочитают ?: но это не очень важно. - person ; 19.02.2016

Сначала вы конвертируете строку, полученную из командной строки, в целое число, проще всего использовать sscanf

e.g.

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

теперь у вас есть десятичное значение n.

Чтобы преобразовать в двоичный файл, вам нужно пройти каждый бит в целом беззнаковом.

Выполняя побитовое и с десятичным значением, вы можете проверить каждый отдельный бит, установлен ли он, и напечатать либо «1», либо «0» в зависимости от бита.

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
   ...
}
person AndersK    schedule 19.02.2016

Вот как может выглядеть ваша программа. Я прокомментировал важные моменты, но вы должны посмотреть документацию по используемым функциям. Если вы используете Linux (похоже, вы судите по исходному вопросу), вы можете использовать «справочные страницы» Linux, такие как man sscanf, чтобы предоставить полную информацию о sscanf или любой другой функции в библиотеке C.

Скомпилируйте его с помощью: 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;
}

Я написал это сам, но в Интернете есть много примеров такого рода «обучающих упражнений», поэтому я не думаю, что будет большим спойлером просто включить здесь дословный код.

person Community    schedule 19.02.2016