2014-12-25 1 views
1

У меня есть матрица (2-D int pointer int **mat), которую я пытаюсь записать в файл в Linux в Little-endian конвенции.Контекст Little-endian и сохранение в двоичном файле

Вот моя функция, которая записывает в файл:

#define BUFF_SIZE 4 
void write_matrix(int **mat, int n, char *dest_file) { 
    int i, j; 
    char buff[BUFF_SIZE]; 
    int fd = open(dest_file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IXUSR); 

    if (fd < 0) { 
     printf("Error: Could not open the file \"%s\".\n", dest_file); 
    } 

    buff[0] = (n & 0x000000ff); 
    buff[1] = (n & 0x0000ff00) >> 8; 
    buff[2] = (n & 0x00ff0000) >> 16; 
    buff[3] = (n & 0xff000000) >> 24; 

    write(fd, buff, BUFF_SIZE); 

    for (i = 0; i < n; i++) { 
     for (j = 0; j < n; j++) { 
      buff[0] = (mat[i][j] & 0x000000ff); 
      buff[1] = (mat[i][j] & 0x0000ff00) >> 8; 
      buff[2] = (mat[i][j] & 0x00ff0000) >> 16; 
      buff[3] = (mat[i][j] & 0xff000000) >> 24; 

      if (write(fd, buff, BUFF_SIZE) != BUFF_SIZE) { 
       close(fd); 
       printf("Error: could not write to file.\n"); 
       return; 
      } 
     } 
    } 

    close(fd); 
} 

Проблема заключается в том, что, когда я пишу из матрицы достаточно формы mat[i][i] = i большой (скажем, 512 X 512), я думаю, что я получаю переполнение, так как я получаю странные отрицательные числа.

Чтобы преобразовать обратно я использую:

void read_matrix(int fd, int **mat, int n, char buff[]) { 
    int i, j; 

    for (i = 0; i < n; i++) { 
     for (j = 0; j < n; j++) { 
      assert(read(fd, buff, BUFF_SIZE) == BUFF_SIZE); 
      mat[i][j] = byteToInt(buff); 
     } 
    } 
} 

int byteToInt(char buff[]) { 
    return (buff[3] << 24) | (buff[2] << 16) | (buff[1] << 8) | (buff[0]); 
} 

Какой я делаю неправильно?

EDITED:

  1. Добавлена ​​функция read_matrix.

  2. Похоже, я получаю short вместо на int, так как 384 = (110000000) становится -128 = (бин) +1000000

  3. Сделала тест, и выяснили, что:

    char c = 128; int i = 0; i | = c;

    i = -128. Зачем????

+1

, пожалуйста, покажите пример вызова этого вопроса, как его настроить? – Jasen

+1

Скорее всего, не проблема, но ваша проверка ошибок является странной. Перед тем, как проверить, будет ли fd меньше нуля, вы пишете файл. – FDinoff

+1

@FDinoff вы правы насчет проверки на успешное открытие ... Исправление! –

ответ

3

Проблема заключается во входной конверсии:

int byteToInt(char buff[]) { 
    return (buff[3] << 24) | (buff[2] << 16) | (buff[1] << 8) | (buff[0]); 
} 

Вы не упоминаете, какую платформу вы, но на большинстве распространенных платформ char подписан. И это вызовет проблемы. Предположим, например, что buff[1] равен 0x80 (0b1000000). Поскольку это знаковое значение, это код для значения -128. И так как операторы сдвига начинают с целых продвижений по обоим их аргументам, которые будут преобразованы в целое число -128 до выполнения операции сдвига; другими словами, он будет иметь значение 0xFFFFFF80, которое после сдвига станет 0xFFFF8000.

Поразрядные логические операторы (такие как |) выполняют обычные арифметические преобразования перед выполнением побитовых операций; в случае (buff[1] << 8) | (buff[0]), левый оператор уже будет знаком int (потому что тип << является типом его , продвинутым левым аргументом); правый аргумент, неявно подписанный char, также будет продвигаться к подписанному int, поэтому снова, если бы он был 0x80, он оказался бы расширенным до 0xFFFFFF80.

В любом случае побитовая операция или операция заканчиваются нежелательными 1-мя разрядами высокого порядка.

Явное литье buff[x] к unsigned int не поможет, потому что сначала будет знаковым расширением на int до того переосмыслили как unsigned int.Вместо этого необходимо, чтобы бросить его в unsigned char:

int byteToInt(char buff[]) { 
    return ((unsigned char)buff[3] << 24) 
      | ((unsigned char)buff[2] << 16) 
      | ((unsigned char)buff[1] << 8) 
      | (unsigned char)buff[0]; 
} 

С int может быть 16 бит, было бы лучше использовать long, и в самом деле было бы лучше использовать unsigned long, чтобы избежать других проблем преобразования. Это означает выполнение двойного литья:

unsigned long byteToInt(char buff[]) { 
    return ((unsigned long)(unsigned char)buff[3] << 24) 
      | ((unsigned long)(unsigned char)buff[2] << 16) 
      | ((unsigned long)(unsigned char)buff[1] << 8) 
      | (unsigned long)(unsigned char)buff[0]; 
} 
+1

'(unsigned char) buff [3] << 24' не переносится. 'unsigned char' продвигается к' int'. C указывает 'int' как на _least_ 16 бит. Если 'int' был 16-битным, то смещение' int' 24 не определено. Но тогда OP, по-видимому, предполагает (неверно) 'int' составляет не менее 32 бит. – chux

+1

@chux: Я считаю, что это теоретически не переносимо, даже если 'int' является 32-битным, потому что нет гарантии, что сдвиг влево положительного подписанного int приведет к ожидаемому отрицательному подписанному int (хотя результат не указан, а не неопределен.) Но это болото слишком глубоко, чтобы впасть в праздник. И у вас есть счастливый :) – rici

+1

Из болотистой пропасти капельница капает с головы, кивая и бормоча: «Да, сдвиньте UB, грязный - очень грязный», когда он бродит обратно к своему логову и зажимает свой чулок. (хорошая шапка) – chux

1

У вас есть неопределенное поведение, которое часто упускается из виду. Левое смещение знаковых отрицательных значений не определено. See here для подробностей.

Когда вы сделаете это

int byteToInt(char buff[]) { 
    return (buff[3] << 24) | (buff[2] << 16) | (buff[1] << 8) | (buff[0]); 
} 

даже если один элемент buff имеет отрицательное значение (т.е. один из значения двоичных данных устанавливает старший бит), то вы попали неопределенное поведение. Поскольку ваши данные двоичные, чтение его как unsigned имеет наибольший смысл. Вы можете использовать стандартный тип, который делает четность и длину подписей, например uint8_t от stdint.h.

Смежные вопросы