Неправильное употребление значения расширенного символа знака

Преобразование типа данных с расширением знака вызывает неожиданное поведение

Описание

Неправильное употребление значения расширенного символа знака происходит, когда вы преобразовываете или простую переменную char со знаком, содержащую возможные отрицательные величины к более широкому целочисленному типу данных (или выполните арифметическую операцию, которая делает преобразование), и затем используйте получившееся значение одним из этих способов:

  • Для сравнения с EOF (использующий == или !=)

  • Как индекс массива

  • В качестве аргумента к обрабатывающей символ функции в ctype.h, например, isalpha() или isdigit()

Если вы преобразовываете переменную char со знаком с отрицательной величиной к более широкому типу, такому как int, знаковый бит сохраняется (расширение знака). Это может привести к определенным проблемам даже в ситуациях, где вы думаете, что объяснили знаковый бит.

Например, значение char со знаком-1 может представлять символьный EOF (конец файла), который является недопустимым символом. Предположим переменная char, var получает это значение. Если вы обрабатываете var как переменную char, вы можете хотеть записать специальный код, чтобы составлять это значение недопустимого символа. Однако, если вы выполняете операцию, такую как var++ (включающее целочисленное продвижение), это приводит к значению 0, который представляет допустимое значение '\0' случайно. Вы перешли от недопустимого до допустимого значения через арифметическую операцию.

Даже для отрицательных величин кроме-1, преобразование от char со знаком до int со знаком может привести к другим проблемам. Например, значение char со знаком-126 эквивалентно значению unsigned char 130 (соответствие расширенному символу '\202'). Если вы преобразовываете значение от char до int, знаковый бит сохраняется. Если вы затем бросаете получившееся значение к unsigned int, вы получаете неожиданно большое значение, 4294967170 (принятие 32-битного int). Если ваш код ожидает значение unsigned char 130 в итоговой переменной unsigned int, вы видите неожиданные результаты.

Первопричиной этой проблемы является расширение знака во время преобразования в более широкий тип. Большая часть архитектуры использует дополнительное представление two для хранения значений. В этом представлении старший значащий бит указывает на знак значения. Когда преобразовано в более широкий тип, преобразование сделано путем копирования этого знакового бита во все ведущие биты более широкого типа, так, чтобы знак был сохранен. Например, значение char-3 представлено как 11111101 (принимающий 8-битный char). Когда преобразовано в int, представление:

11111111 11111111 11111111  11111101
Значение-3 сохраняется в более широком типе int. Однако, когда преобразовано в unsigned int, значение (4294967293) больше не является тем же самым как unsigned char, эквивалентным из исходного значения char. Если вы не знаете об этой проблеме, вы видите неожиданные результаты в своем коде.

Риск

В следующих случаях Средство поиска Ошибки отмечает использование переменных после преобразования от char до более широкого типа данных или арифметической операции, которая неявно преобразовывает переменную в более широкий тип данных:

  • Если вы сравниваете значение переменных с EOF:

    Значение char-1 может представлять недопустимый символ EOF или допустимое значение расширенного символа '\377' (соответствующий эквивалентному unsigned char, 255). После того, как переменная char брошена к более широкому типу, такому как int из-за расширения знака, значение char-1, представляя один из EOF или '\377' становится значением int-1, представляя только EOF. Значение unsigned char 255 больше не может восстанавливаться с переменной int. Средство поиска ошибки отмечает эту ситуацию так, чтобы можно было бросить переменную к unsigned char сначала (или избежать char-to-int преобразование или операция преобразования перед сравнением с EOF). Только затем сравнение с EOF значимо. Смотрите Значение Расширенного символа Знака По сравнению с EOF.

  • Если вы используете значение переменных в качестве индекса массива:

    После того, как переменная char брошена к более широкому типу, такому как int из-за расширения знака, все отрицательные величины сохраняют свой знак. Если вы используете отрицательные величины непосредственно, чтобы получить доступ к массиву, вы вызываете переполнение буфера / потеря значимости. Даже когда вы объясняете отрицательные величины, способ, которым вы объясняете их, может привести к неправильным элементам, считанным из массива. Смотрите Значение Расширенного символа Знака, Используемое в качестве Индекса массива.

  • Если вы передаете значение переменных в качестве аргумента к обрабатывающей символ функции:

    Согласно стандарту C11 (Раздел 7.4), если вы предоставляете целочисленный аргумент, который не может быть представлен как unsigned char или EOF, получившееся поведение не определено. Средство поиска ошибки отмечает эту ситуацию, потому что отрицательные значения char после преобразования больше не могут представляться как unsigned char или EOF. Например, значение char со знаком-126 эквивалентно значению unsigned char 130, но значение int со знаком-126 не может быть представлено как unsigned char или EOF.

Фиксация

Перед преобразованием в более широкий целочисленный тип данных, бросок со знаком или плоскость значение char явным образом к unsigned char.

Если вы используете тип данных char, чтобы не представлять символы, но просто как меньший тип данных, чтобы сохранить память, ваше использование расширенных знаком значений char может избежать рисков, упомянутых ранее. Если так, добавьте комментарии в свой результат или код, чтобы избежать другого анализа. Смотрите Результаты Polyspace Адреса Через Исправления ошибок или Комментарии.

Примеры

развернуть все

#include <stdio.h>
#include <stdlib.h>
#define fatal_error() abort()

extern char parsed_token_buffer[20];

static int parser(char *buf)
{
    int c = EOF;
    if (buf && *buf) {
        c = *buf++;    
    }
    return c;
}

void func()
{
    if (parser(parsed_token_buffer) == EOF) { 
        /* Handle error */
        fatal_error();
    }
}

В этом примере функциональный parser может пересечь вход buf строки. Если символ в строке имеет значение-1, это может представлять или EOF или значение допустимого символа '\377' (соответствующий unsigned char эквивалентные 255). Когда преобразовано в переменную int c, его значение становится целочисленным значением-1, который всегда является EOF. Более позднее сравнение с EOF не обнаружит, если значением, возвращенным от parser, будет на самом деле EOF.

Исправление — бросок к unsigned char перед преобразованием

Одно возможное исправление должно бросить плоскость значение char к unsigned char перед преобразованием в более широкий тип int. Только затем можете вы тестировать, если возвращаемым значением parser является действительно EOF.

#include <stdio.h>
#include <stdlib.h>
#define fatal_error() abort()

extern char parsed_token_buffer[20];

static int parser(char *buf)
{
    int c = EOF;
    if (buf && *buf) {
        c = (unsigned char)*buf++;    
    }
    return c;
}

void func()
{
    if (parser(parsed_token_buffer) == EOF) { 
        /* Handle error */
        fatal_error();
    }
}
#include <limits.h>
#include <stddef.h>
#include <stdio.h>

#define NUL '\0'
#define SOH 1    /* start of heading */
#define STX 2    /* start of text */
#define ETX 3    /* end of text */
#define EOT 4    /* end of transmission */
#define ENQ 5    /* enquiry */
#define ACK 6    /* acknowledge */

static const int ascii_table[UCHAR_MAX + 1] =
{
      [0]=NUL,[1]=SOH, [2]=STX, [3]=ETX, [4]=EOT, [5]=ENQ,[6]=ACK,
      /* ... */
      [126] = '~',
      /* ... */
      [130/*-126*/]='\202',
      /* ... */
      [255 /*-1*/]='\377'
};

int lookup_ascii_table(char c)
{
    int i;
    i = (c < 0 ? -c : c);
    return ascii_table[i];
}

В этом примере переменная char c преобразован в переменную int i. Если c имеет отрицательные величины, они преобразованы в положительные значения перед присвоением на i. Однако это преобразование может привести к неожиданным значениям, когда i используется в качестве индекса массива. Например:

  • Если c имеет значение-1 представление недопустимого символа EOF, вы хотите, вероятно, обработать это значение отдельно. Однако в этом примере, значение c, равного-1, приводит к значению i, равного 1. Функциональный lookup_ascii_table возвращает значение ascii_table[1] (или SOH) без значения недопустимого символа составляемый EOF.

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

  • Если c имеет отрицательную величину, когда присвоено i, его знак инвертируется. Однако, если вы получаете доступ к элементам ascii_table через i, это реверсирование знака может привести к неожиданным считанным значениям.

    Например, если c имеет значение-126, i имеет значение 126. Функциональный lookup_ascii_table возвращает значение ascii_table[126] (или '~'), но вы, вероятно, ожидали значение ascii_table[130] (или '\202').

Исправление – бросок к unsigned char

Чтобы исправить проблемы, избегайте преобразования от char до int. Во-первых, проверяйте c на значение EOF. Затем бросьте значение переменной char c к unsigned char и используйте результат в качестве индекса массива.

#include <limits.h>
#include <stddef.h>
#include <stdio.h>

#define NUL '\0'
#define SOH 1    /* start of heading */
#define STX 2    /* start of text */
#define ETX 3    /* end of text */
#define EOT 4    /* end of transmission */
#define ENQ 5    /* enquiry */
#define ACK 6    /* acknowledge */

static const int ascii_table[UCHAR_MAX + 1] =
{
      [0]=NUL,[1]=SOH, [2]=STX, [3]=ETX, [4]=EOT, [5]=ENQ,[6]=ACK,
      /* ... */
      [126] = '~',
      /* ... */
      [130/*-126*/]='\202',
      /* ... */
      [255 /*-1*/]='\377'
};

int lookup_ascii_table(char c)
{
    int r = EOF;
    if (c != EOF) /* specific handling EOF, invalid character */
        r = ascii_table[(unsigned char)c]; /* cast to 'unsigned char' */
    return r;
}

Информация о результате

Группа: Программирование
Язык: C | C++
Значение по умолчанию: на
Синтаксис командной строки: CHARACTER_MISUSE
Влияние: носитель
ID CWE: 704

Введенный в R2017a