Подтвердить что ты не робот

Могу ли я включить unsigned char в char и наоборот?

Я хочу использовать функцию, которая ожидает такие данные:

void process(char *data_in, int data_len);

Поэтому он просто обрабатывает некоторые байты.

Но мне удобнее работать с "unsigned char", когда дело доходит до необработанных байтов (он как-то "чувствует" больше права иметь дело только с положительными 0 до 255 значений), поэтому мой вопрос:

Можно ли безопасно передать unsigned char * в эту функцию?

Другими словами:

  • Гарантируется ли я безопасно конвертировать (отличать) между char и unsigned char по желанию без потери информации?
  • Можно ли безопасно конвертировать (отличать) между указателями на char и без знака char по желанию без потери информации?

Бонус: есть ли ответ на C и С++?

4b9b3361

Ответ 1

Короткий ответ - да, если вы используете явный приведение, но для его подробного объяснения есть три аспекта:

1) Законность преобразования
Преобразование между signed T* и unsigned T* (для некоторого типа T) в любом направлении обычно возможно, потому что исходный тип может быть сначала преобразован в void * (это стандартное преобразование, §4.10), а void * может быть преобразован в тип назначения с использованием явного static_cast (§5.2.9/13):

static_cast<unsigned char*>(static_cast<void *>(data_in))

Это может быть сокращено (§5.2.10/7) как

reinterpret_cast<unsigned char *>(data_in)

потому что char является стандартным макетом (§3.9.1/7.8 и §3.9/9), а подпись не меняет выравнивание (§3.9.1/1). Его также можно записать в виде C-стиля:

(unsigned char *)(data_in)

Опять же, это работает в обоих направлениях: от unsigned* до signed* и обратно. Существует также гарантия, что если вы примените эту процедуру в одну сторону, а затем обратно, значение указателя (то есть адрес, на который он указывает) не изменится (§5.2.10/7).

Все это относится не только к преобразованиям между signed char * и unsigned char *, но также к char */unsigned char * и char */signed char *, соответственно. (char, signed char и unsigned char формально три различных типа, §3.9.1/1.)

Чтобы быть ясным, не имеет значения, какой из трех методов-бросков вы используете, но вы должны использовать его. Простое перенос указателя не будет работать, поскольку преобразование, хотя и законное, не является стандартным преобразованием, поэтому оно не будет выполняться неявно (компилятор выдаст ошибку, если попытается).

2) Четкость доступа к значениям
Что произойдет, если внутри функции вы разыщите указатель, т.е. Выполните *data_in, чтобы получить значение gl для базового символа; является ли это четко определенным и законным? Соответствующим правилом является правило строгого сглаживания (§3.10/10):

Если программа пытается получить доступ к сохраненному значению объекта через значение gl другого, чем одно из следующих типов, поведение undefined:

  • [...]
  • тип, который является подписанным или неподписанным типом, соответствующим динамическому типу объекта,
  • [...]
  • a char или unsigned char.

Следовательно, доступ к signed char (или char) через unsigned char* (или char) и наоборот не запрещен этим правилом – вы должны иметь возможность сделать это без проблем.

3) Результирующие значения
После дефрагментации указателя, преобразованного по типу, сможете ли вы работать со значением, которое вы получаете? Важно иметь в виду, что преобразование и разыменование указателя, описанного выше, представляет собой повторную интерпретацию (не изменяющуюся!) Битовой диаграммы, сохраненной по адресу символа. Итак, что происходит, когда бит-шаблон для подписанного символа интерпретируется как символ без знака (или наоборот)?

При переходе от без знака к подписанному типичным эффектом будет то, что для значений от 0 до 128 ничего не происходит, а значения выше 128 становятся отрицательными. Аналогично в обратном порядке: при переходе от подписанного к unsigned отрицательные значения будут отображаться как значения, превышающие 128.

Но такое поведение на самом деле не гарантируется Стандартом. Единственное, что гарантируется Стандартом, это то, что для всех трех типов char, unsigned char и signed char для представления значения используются все биты (не обязательно 8, бит. Wt). Поэтому, если вы интерпретируете один как другой, сделайте несколько копий, а затем сохраните их обратно в исходное местоположение, вы можете быть уверены, что потеря информации не будет (как вам нужно), но вы не обязательно будете знать, какие значения фактически означает (по крайней мере, не полностью переносимым образом).

Ответ 2

unsigned char или signed char является просто интерпретацией: конверсии не происходит.

Поскольку вы обрабатываете байты, чтобы показать намерение, было бы лучше объявить как

void process(unsigned char *data_in, int data_len);

[Как отмечено редактором: простой char может быть либо подписанным, либо беззнаковым типом. Стандарты C и С++ явно разрешают либо (он всегда является отдельным типом от unsigned char или signed char, но имеет тот же диапазон, что и один из них)]

Ответ 3

Вы можете передать указатель на другой тип char, но вам может потребоваться явно его бросить. Указатели гарантированно будут иметь одинаковый размер и одинаковые значения. Во время конверсии не будет никакой потери информации.

Если вы хотите преобразовать char в unsigned char внутри функции, просто присвойте значение char переменной unsigned char или внесите значение char в unsigned char.

Если вам нужно преобразовать unsigned char в char без потери данных, это немного сложнее, но все же возможно:

#include <limits.h>

char uc2c(unsigned char c)
{
#if CHAR_MIN == 0
  // char is unsigned
  return c;
#else
  // char is signed
  if (c <= CHAR_MAX)
    return c;
  else
    // ASSUMPTION 1: int is larger than char
    // ASSUMPTION 2: integers are 2 complement
    return c - CHAR_MAX - 1 - CHAR_MAX - 1;
#endif
}

Эта функция преобразует unsigned char в char таким образом, что возвращаемое значение может быть преобразовано обратно в то же значение unsigned char в качестве параметра.

Ответ 4

Вам действительно нужно просмотреть код до process(), чтобы узнать, можете ли вы безопасно передавать символы без знака. Если функция использует символы в качестве индекса в массиве, то нет, вы не можете использовать неподписанные данные.

Ответ 5

Семантически, передача между unsigned char * и char * безопасна и даже несмотря на то, что они перебрасываются между ними, как в С++.

Однако рассмотрим следующий пример кода:

#include "stdio.h"

void process_unsigned(unsigned char *data_in, int data_len) {
    int i=data_len;
    unsigned short product=1;

    for(; i--; product*=data_in[i]) 
        ;

    for(i=sizeof(product); i--; ) {
        data_in[i]=((unsigned char *)&product)[i];
        printf("%d\r\n", data_in[i]);
    }
}

void process(char *data_in, int data_len) {
    int i=data_len;
    unsigned short product=1;

    for(; i--; product*=data_in[i]) 
        ;

    for(i=sizeof(product); i--; ) {
        data_in[i]=((unsigned char *)&product)[i];
        printf("%d\r\n", data_in[i]);
    }
}

void main() {
    unsigned char 
        a[]={1, -1}, 
        b[]={1, -1};

    process_unsigned(a, sizeof(a));
    process(b, sizeof(b));
    getch();
}

выход:

0
255
-1
-1

Весь код внутри process_unsigned и process равен ИДЕНТИЧНО. Единственное различие - неподписанное и подписанное. Этот пример показывает, что код в черном ящике зависит от SIGN и ничего не гарантируется между вызывающим абонентом и вызывающим абонентом.

Таким образом, я бы сказал, что он применим только к передаче, но ни одна из других возможностей не гарантируется.

Ответ 6

Да, вы всегда можете конвертировать из char в unsigned char и наоборот без проблем. Если вы запустите следующий код и сравните его с таблицей ASCII (ссылка http://www.asciitable.com/), вы можете увидеть доказательство самостоятельно и как C/С++ справляется с преобразованиями - они имеют дело точно так же:

#include "stdio.h"


int main(void) {
    //converting from char to unsigned char
    char c = 0;
    printf("%d byte(s)\n", sizeof(char));  // result: 1byte, i.e. 8bits, so there are 2^8=256 values that a char can store.
    for (int i=0; i<256; i++){
        printf("int value: %d - from: %c\tto: %c\n", c,  c, (unsigned char) c);
        c++;
    }

    //converting from unsigned char to char
    unsigned char uc = 0;
    printf("\n%d byte(s)\n", sizeof(unsigned char));
    for (int i=0; i<256; i++){
        printf("int value: %d - from: %c\tto: %c\n", uc, uc, (char) uc);
        uc++;
    }
}

Я не буду публиковать вывод, потому что у него слишком много строк! На выходе можно заметить, что в первой половине каждого раздела, то есть от я = 0: 127, преобразование из символов в беззнаковые символы и наоборот хорошо работает без каких-либо изменений или потерь.

Однако из я = 128: 255 символы и символы без знака не могут быть выбраны или у вас будут разные выходы, потому что unsigned char сохраняет значения от [0: 256] и char сохраняет значения в интервал [-128: 127]). Тем не менее, поведение в этой второй половине не имеет значения, потому что в C/С++, в общем, вы приводите только символы char/unsigned в качестве символов ASCII, которые могут принимать только 128 разных значений, а остальные 128 значений (положительные для символов или отрицательные для неподписанных символов) никогда не используются.

Если вы никогда не ставите значение в char, которое не представляет символ, и вы никогда не ставите значение в unsigned char, которое не представляет символ, все будет ОК!

Дополнительно: даже если вы используете UTF-8 или другие кодировки (для специальных символов) в своих строках с C/С++, все с таким типом приведения будет ОК, например, используя кодировку UTF-8 (ссылка http://lwp.interglacial.com/appf_01.htm):

char hearts[]   = {0xe2, 0x99, 0xa5, 0x00};
char diamonds[] = {0xe2, 0x99, 0xa6, 0x00};
char clubs[]    = {0xe2, 0x99, 0xa3, 0x00};
char spades[]   = {0xe2, 0x99, 0xa0, 0x00};
printf("hearts (%s)\ndiamonds (%s)\nclubs (%s)\nspades (%s)\n\n", hearts, diamonds, clubs, spades);

вывод этого кода будет:
сердца (♥)
бриллианты (♦)
клубы (♣)
лопаты (♠)

даже если вы отбросили каждый из своих символов на неподписанные символы.

так:

  • "могу ли я всегда безопасно передать беззнаковое char * в эту функцию?" да!

  • "гарантировано ли, что я могу безопасно конвертировать (отличать) между char и unsigned char по желанию без потери информации?" да!

  • "можно ли безопасно конвертировать (отличать) между указателями на char и без знака char по желанию без потери информации?" да!

  • "является ответом на C и С++?" да!