Какие практические применения для переменных с константой в C? - PullRequest
5 голосов
/ 08 августа 2010

Как обсуждалось в нескольких недавних вопросах, объявление const -квалифицированных переменных в C (в отличие от const переменных в C ++ или указателей на const в C) обычно служит очень малой цели. Самое главное, они не могут использоваться в константных выражениях .

С учетом сказанного, каковы некоторые законные варианты использования const квалифицированных переменных в C? Я могу вспомнить некоторые из них, которые недавно появились в коде, с которым я работал, но, безусловно, должны быть и другие. Вот мой список:

  • Использование их адресов в качестве специальных дозорных значений для указателя, чтобы никогда не сравнивать их с любым другим указателем. Например: char *sentinel(void) { static const char s; return &s; } или просто const char sentinel[1]; Поскольку мы заботимся только об адресе и фактически не имеет значения, был ли объект записан, единственное преимущество const заключается в том, что компиляторы обычно хранят его только для чтения. память, поддерживаемая mmap исполняемого файла или копией нулевой страницы.

  • Использование const квалифицированных переменных для экспорта значений из библиотеки (особенно совместно используемых библиотек), когда значения могут изменяться с новыми версиями библиотеки. В таких случаях простое использование #define в заголовке интерфейса библиотеки не будет хорошим подходом, поскольку это сделает приложение зависимым от значений констант в конкретной версии библиотеки, с которой оно было построено.

  • Тесно связан с предыдущим использованием, иногда вы хотите представить предопределенные объекты из библиотеки приложению (наиболее существенными примерами являются stdin, stdout и stderr из стандартной библиотеки) , Используя этот пример, extern FILE __stdin; #define stdin (&__stdin) будет очень плохой реализацией из-за того, что большинство систем реализуют разделяемые библиотеки - обычно они требуют, чтобы весь объект (здесь FILE) был скопирован по адресу, определенному, когда приложение связано, и ввести зависимость от размера объекта (программа прекратит работу, если библиотека будет перестроена и размер объекта изменится). Использование указателя const (не указатель на const) устраняет все проблемы: extern FILE *const stdin;, где указатель const инициализируется так, чтобы указывать на предопределенный объект (который сам, вероятно, объявлен * 1036) *) где-то внутри библиотеки.

  • Поиск таблиц для математических функций, свойств символов и т. Д. Это очевидная, которую я изначально забыл включить, вероятно, потому что я думал об отдельных const переменных типа арифметики / указателя, так как именно в этом вопрос Тема впервые возникла. Спасибо Эйдану за то, что он заставил меня вспомнить.

  • Как вариант на таблицах поиска, реализация конечных автоматов. В ответ Эйдан привел подробный пример. Я обнаружил, что та же концепция часто очень полезна без указателей на функции, если вы можете кодировать поведение / переходы из каждого состояния в терминах нескольких числовых параметров.

У кого-нибудь еще есть умные, практичные применения для const -качественных переменных в C?

Ответы [ 6 ]

6 голосов
/ 08 августа 2010

const довольно часто используется во встроенном программировании для отображения на контакты GPIO микроконтроллера. Например:

typedef unsigned char const volatile * const tInPort;
typedef unsigned char                * const tOutPort;

tInPort  my_input  = (tInPort)0x00FA;
tOutPort my_output = (tOutPort)0x00FC;

Оба этих параметра не позволяют программисту случайно изменить сам указатель, что в некоторых случаях может иметь катастрофические последствия. Декларация tInPort также не позволяет программисту изменять вводимое значение.

Использование volatile не позволяет компилятору предполагать, что самое последнее значение для my_input будет существовать в кэше. Поэтому любое чтение с my_input будет идти непосредственно на шину и, следовательно, всегда считываться с выводов ввода-вывода устройства.

2 голосов
/ 08 октября 2010

Предупреждение PC-lint 429 следует из ожидания, что локальный указатель на выделенный объект должен быть использован

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

Под «грязным» я подразумеваю функцию, соответствующий параметр указателя которой имеет неконстантный базовый тип. Описание предупреждения освобождает библиотечные функции, такие как strcpy () от «грязной» метки, по-видимому, потому что ни одна из таких библиотечных функций не становится владельцем указанного объекта.

Таким образом, при использовании инструментов статического анализа, таких как PC-lint, спецификатор const параметров вызываемых функций позволяет учитывать локально выделенные области памяти.

2 голосов
/ 08 августа 2010

Например:

void memset_type_thing(char *first, char *const last, const char value) {
    while (first != last) *(first++) = value;
}

То, что last не может быть частью константного выражения, ни здесь, ни там. const является частью системы типов, используемой для указания переменной, значение которой не изменится. В моей функции нет причины изменять значение last, поэтому я объявляю его const.

Я не мог не объявить его const, но тогда я вообще не мог использовать статически типизированный язык; -)

1 голос
/ 08 августа 2010

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

typedef enum { STATE1, STATE2, STATE3 } FsmState;
struct {
    FsmState State;
    int (*Callback)(void *Arg);
} const FsmCallbacks[] = {
    { STATE1, State1Callback },
    { STATE2, State2Callback },
    { STATE3, State3Callback }
};

int dispatch(FsmState State, void *Arg) {
    int Index;
    for(Index = 0; Index < sizeof(FsmCallbacks)/sizeof(FsmCallbacks[0]); Index++)
        if(FsmCallbacks[Index].State == State)
            return (*FsmCallbacks[Index].Callback)(Arg);
}

Это похоже на что-то вроде:

int dispatch(FsmState State, void *Arg) {
    switch(State) {
        case STATE1:
            return State1Callback(Arg);
        case STATE2:
            return State2Callback(Arg);
        case STATE3:
            return State3Callback(Arg);
    }
}

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

struct {
    FsmState State;
    int (*Callback)(void *Arg);
    void (*Abort)(void *Arg);
} const FsmCallbacks[] = {...};

и мне не нужно изменять обе подпрограммы abort и dispatch для нового состояния. Я использую const для предотвращения изменения таблицы во время выполнения.

1 голос
/ 08 августа 2010
  • Переменная const полезна, когда тип не тот, который имеет пригодные для использования литералы, т. Е. Что-либо кроме числа.Для указателей вы уже привели пример (stdin и со), где вы можете использовать #define, но вы получите lvalue, который можно легко назначить.Другой пример - типы struct и union, для которых нет присваиваемых литералов (только инициализаторы).Рассмотрим, например, разумную реализацию комплексных чисел в C89:

    typedef struct {double Re; double Im;} Complex;
    const Complex Complex_0 = {0, 0};
    const Complex Complex_I = {0, 1}; /* etc. */
    
  • Иногда вам просто нужно иметь сохраненный объект, а не литерал, потому что вам нужно передать данные в полиморфныйфункция, которая ожидает void* и size_t.Вот пример из cryptoki API (он же PKCS # 11): многим функциям требуется список аргументов, передаваемый в виде массива CK_ATTRIBUTE, который в основном определяется как

    typedef struct {
        CK_ATTRIBUTE_TYPE type;
        void *pValue;
        unsigned long ulValueLen;
    } CK_ATTRIBUTE;
    typedef unsigned char CK_BBOOL;
    

    поэтому в вашем приложении для атрибута с логическим значением необходимо передать указатель на байт, содержащий 0 или 1:

    CK_BBOOL ck_false = 0;
    CK_ATTRIBUTE template[] = {
        {CKA_PRIVATE, &ck_false, sizeof(ck_false)},
    ... };
    
0 голосов
/ 08 августа 2010

Они могут использоваться для отображаемых в памяти периферийных устройств или регистров, которые не могут быть изменены пользовательским кодом, только некоторым внутренним механизмом микропроцессора.Например.на PIC32MX определенные регистры, указывающие состояние программы, имеют квалификацию const volatile - так что вы можете прочитать их, и компилятор не будет пытаться оптимизировать, скажем, повторный доступ, но ваш код не сможет записать в них.

(У меня нет никакого кода, поэтому я не могу привести хороший пример прямо сейчас.)

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