Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: CPP enum с минусовыми значениями
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
k155la3
Вопрос касается кода возврата функции, а именно удачно или нет она отработала.
Если плюс - удачно, информация в коде возврата - справочная.
Если минус - критичная ошибка, ф-ия не отработала.

(?) Насколько такой подход (с отрицательным enum) правильный-феншуйный-политкорректный ?

ps - minus_4 итд приведено для наглядности, в реале - ест-но - там симв. имя ошибки вроде eErr_ADC_Ready

А реализовал так:
Код
enum TEnum
{
    minus_4 = -4,
    minus_3,
    minus_2,
    minus_1,
    null_0 = 0,
    plus_1,
    plus_2,
    plus_3,
    plus_4
} my_enum;

. . .

int MyFun(void) {    ... ... ...   return(minus_2); ..... .... return(plus_3); }

. . .

RetCode = MyFun();

if( RetCode > 0 )
{
   . . . .  ошибок нет, в RetCode в инф. о статусе выполнения.
}
else  // минусы и 0
{
   . . . .  разбор ошибок
}
novikovfb
Почему бы и нет. Одно не понятно: чем запись minus_4 лучше простой константы -4? Логичнее было бы вместо minus_4 указать наименование типа ошибки.
k155la3
Цитата(novikovfb @ May 25 2017, 09:32) *
Почему бы и нет. Одно не понятно: чем запись minus_4 лучше простой константы -4? Логичнее было бы вместо minus_4 указать наименование типа ошибки.

Константы надо "изобретать" вручную, а меня это утомляет sm.gif
Кроме того с символическими именами которые предоставляет enum работать в миллион раз удобнее чем с цифровыми кодами.

ps - пардон, я может неверно сформулировал в примере кода. там minus_4 приведено для наглядности.
в реале - ест-но - там симв. имя ошибки вроде eErr_ADC_Ready
_pv
Цитата
integer constant expression whose value is representable as a value of type int

k155la3
"integer constant expression whose value is representable as a value of type int"
- это в смысле что int по умолчанию signed, соответственно минус можно использовать.
Насколько я понял.

По синтаксису компилятор это, естественно, допускает.

Интересует "практика" использования отрицательных enum.
AnatolyT
Постоянно применяю, только не enum, а int. Обычная практика, если функция возвращает отрицательное значение, это тип ошибки, если ноль или положительное значение, то все Ок или результат. С enum не пробовал, а зачем, int можно преобразовать или интерпретировать практически любым типом, как целое, символ или логическое значение. Удобно во вложенных функциях, возвращает тип ошибки на самый верх.
k155la3
Цитата(AnatolyT @ May 25 2017, 11:00) *
. . . .
Удобно во вложенных функциях, возвращает тип ошибки на самый верх.

Ok.

Я при "транизитном" возврате по ошибке, при каждом выходе "вверх", код ошибки умножаю на 10 - тогда видно с какого уровня
"всплыло". Более правильно наверно, использовать исключения, но пока делаю так.


AlexRayne
Цитата(k155la3 @ May 25 2017, 11:32) *
Интересует "практика" использования отрицательных enum.

нормальная практика.
более интерено енум использовать с псевдонимами и комбинациями - например объявляеш флаги:
еА = 1, еБ = 2, еВ=4. еХ=0x10
и тут же объявляеш псевдонимы комбинаций
еЧеготоОбычное = еА | еХ
eНеобычное = еБ
еОшибка = еА |еБ |еХ
тожесамое = eНеобычное | еЧеготоОбычное

Меня другой момент использования енумов занимает - последнее время вижу что народ константы перечисления делает большими буквами, что обычно выглядит как макро.
k155la3
Цитата(AlexRayne @ May 25 2017, 12:46) *
. . .
(1) более интерено енум использовать с псевдонимами и комбинациями - например объявляеш флаги
. . .
(2) Меня другой момент использования енумов занимает - последнее время вижу что народ константы перечисления делает большими буквами, что обычно выглядит как макро.

(1) да, допустим определены 10 констант - enum. При этом, если int 16-битный, задействовано только 4 разряда и то не полностью.
Оставшиеся 12 разрядов можно использовать для любой полезной инф - битовые флаги, поля итд. Фактически - структура.
Отрицательные значения - это один из вариантов этого.

(2) есть вроде не-дефакто-стандарты на именование различных типов. Тутуж кто-во-что горазд.
A - чтоб мне (любимому) было понятно
B - один из стандартов.
AB или BA приоритет ставят по разному.
AlexRayne
Цитата(k155la3 @ May 25 2017, 14:35) *
(1) да, допустим определены 10 констант - enum. При этом, если int 16-битный, задействовано только 4 разряда и то не полностью.
Оставшиеся 12 разрядов можно использовать для любой полезной инф - битовые флаги, поля итд. Фактически - структура.

да, легкая альтернатива структуре.
Цитата(k155la3 @ May 25 2017, 14:35) *
Отрицательные значения - это один из вариантов этого.

ну вариант не структуры, просто отрицательные числа используются. и на моей практике - как индикатор ошибки.
Сергей Борщ
QUOTE (AlexRayne @ May 25 2017, 12:46) *
Меня другой момент использования енумов занимает - последнее время вижу что народ константы перечисления делает большими буквами, что обычно выглядит как макро.
Ну по смыслу такой enum эквивалентен набору макросов
CODE
#define TAG1 VALUE1
#define TAG2    (TAG1 + 1)
#define TAG3    (TAG2 + 1)
поэтому вполне ожидаемо и аналогичное правило записи имен.
x893
По смыслу - enum это набор неизменяемых именованных значений, по умолчанию int, но можно и другого.
А вель можно еще её сделать типом и использовать в параметрах, тогда людям понятнее что передается и что возращается.
int Foo(..);
или
ERROR_STATUS Foo(...)

А разве это не написано в книжках ?

#define можно переопределить, а enum не получится.
jcxz
Список ошибок часто определяю так:
enum {ERR_1 = 1, ERR_2, ERR_3, ERR_n}; //ну или от 0
А уж возврат ошибочного значения из функции тогда или:
return ERR_...;
или:
return -ERR_...;
А если нужно возвращать из функции или данный код ошибки или корректное состояние, заданное тоже enum-ом, то делаю иногда так:
enum {OK_0, OK_1, OK_2, OK_n};
return (int)OK_1; //корректное завершение с кодом OK_1
return (int)OK_n + (int)ERR_1 - 1; //завершение с ошибкой ERR_1
Т.е. - возвращаются всегда положительные значения. Для Thumb2 такой способ оптимальнее. cool.gif Кто знает асм - поймёт.

Цитата(k155la3 @ May 25 2017, 12:35) *
Оставшиеся 12 разрядов можно использовать для любой полезной инф - битовые флаги, поля итд. Фактически - структура.

Если нужно вернуть 2 параметра, то лучше и возвращать их как 2, а не лепить из них одно в точке return, и не распаковывать потом в точке вызова.
Если мне нужно например на 32-битной архитектуре вернуть 2 значения из функции, я делаю так:
u64 FuncA() {
u64 q = (u32)val1 | (u64)(u32)val2 << 32;
return q;
}
Если пытаться всё лепить в одно 32-битное значение, то будет куча лишних команд при упаковке и потом - при распаковке. А в вышеприведённом случае - минимум команд.
k155la3
Цитата(Сергей Борщ @ May 25 2017, 15:47) *
Ну по смыслу такой enum эквивалентен набору макросов
Код
#define TAG1 VALUE1
#define TAG2    (TAG1 + 1)
#define TAG3    (TAG2 + 1)
поэтому вполне ожидаемо и аналогичное правило записи имен.

По результату - да. По обработке и возможным "засадом" отличается.
enum просчитываются компилятором "за раз", а макросы - это головная боль препроцессора.
Так на какомнибуть TAG756 (сделанный копипастом) sm.gif на вход компилятора попадет:
Код
+ (TAG1)  
+ (TAG1+1) // TAG2
+ ((TAG1 + 1) + 1)  // TAG3
+ (((TAG1 + 1) + 1) + 1 ) // . . . .

... на шаге N у препроцессора (или у компилятора) срабатывает ограничение на длину строки (или списка).
(Это факт, по крайней мере для IAR/MSP430 5.40)
Причем никаких намеков ни Err, ни даже Warn компилятор не выдал.



Цитата(x893 @ May 25 2017, 19:36) *
. . . .
А разве это не написано в книжках ?

Да у меня вопрос собст-но про отрицательные enum. В литературе как-то не натыкался на это.
А проводить гугл-сеанс нет времени. Тем более можно спросить у "первоисточников" на этом форуме sm.gif


Цитата(jcxz @ May 25 2017, 23:47) *
Список ошибок часто определяю так:
. . . .

Ok - спасибо за инф. Надо осмыслить.

AnatolyT
Непонятно какие преимущества дает использование enum как типа возвращаемого значения функции.
novikovfb
Цитата(AnatolyT @ May 26 2017, 13:11) *
Непонятно какие преимущества дает использование enum как типа возвращаемого значения функции.

1. компилятор проверит тип, если есть 2 различных функции, возвращающих коды завершения, имеющих разные наборы значений (и разные enum) - при перепутывании будет предупреждение.
2. символические константы содержат намек на смысловое значение кода, меньше гадать, что означает возврат кода 4.
Сергей Борщ
QUOTE (novikovfb @ May 26 2017, 12:46) *
при перепутывании будет предупреждение.
В плюсах. В обычных Сях enum неявно приводится к int и обратно, поэтому предупреждения, насколько помню, не будет.

Добавлено: перечитал название темы...
k155la3
Цитата(AnatolyT @ May 26 2017, 12:11) *
Непонятно какие преимущества дает использование enum как типа возвращаемого значения функции.

Кроме очевидных преимуществ-удобств разгружающих моск.
Если Вы определили возвр. значение с типом enum, то при работе на отладке
Вы в Watch-окне будете видеть не абстрактные 12345 (и декодировать их вручную),
а символическое имя - в удобо-читаемом-понимаемом виде.

sigmaN
По-моему идеально использовать enum по возможности так, чтобы было вообще всё равно какое там у него значение.
Оно так автоматически и получается, если везде работать с enum как с типом, а не пользоваться тем, что он может быть неявно преобразован в int.

Если все ошибки в вашей системе перечислены в enum и вам не нужно обеспечивать совместимость со сторонним кодом, где у ошибок есть конкретные int значения, то определите для ошибок тип
Цитата
typedef enum{
ERR_OK,
ERR_SOMETHING,
ERR_SOMETHING2
} Error_t;


Пусть все функции будут возвращать Error_t, а обработчики ошибок везде будут реагировать на ошибки путем обращения к ним по именам ERR_OK, ERR_SOMETHING, ERR_SOMETHING2 то всё будет отлично.
Код
switch( error)
{
case ERR_OK: делаем что-то
break;
case ERR_SOMETHING:
break;
case ERR_SOMETHING2:
break;
}

Это будет работать вне зависимости от значений энумов.

Eсли функция возвращает int а мы делаем return ERR_SOMETHING; который вообще-то Error_t - это дурной тон.
Потом чтобы обработать эту ошибку нам надо этот инт опять кастить в enum и начинается каша. Потом кто-то подставит вместо ERR_SOMETHING магическое число какое-нибудь и оно работает. Через год в энум Error_t доавбяют сверху пару новых кодов ошибок, значения все съезжают и то самое магическое число начинает указывать на другую ошибку. Начинается очень большое веселье.

Систему типов компилятора надо использовать, а не бороться с ней!
AnatolyT
Может быть чтобы не было соблазна добавить в начало enum новый код ошибки лучше однозначно закрепить код ошибки в define, потом все равно разбирать его в switch case. Другое дело если используем enum, тогда можно одновременно вести массив строк с сообщениями об ошибках, в порядке соответствующем enum, хотя тоже самое можно делать и с кодами определенными в define.
sigmaN
Цитата
Может быть чтобы не было соблазна добавить в начало enum новый код ошибки лучше однозначно закрепить код ошибки в define,
Может, если в коде не будет ненужных приведений типов и магических цифр - можно доавбялть в энумы что угодно и куда угодно?

Enum - это не define. Дискутировать в плоскости enum vs define не корректно.
Так можно и до define vs template дойти.... это всё глупости.
AlexRayne
Цитата(sigmaN @ May 26 2017, 13:10) *
Код
typedef enum{
ERR_OK,
ERR_SOMETHING,
ERR_SOMETHING2
} Error_t;

sigmaN: а почему Вы большими буквами значения енума определяете?
k155la3
Цитата(AlexRayne @ May 26 2017, 16:33) *
sigmaN: а почему Вы большими буквами значения енума определяете?

Ну например, #def в проекте мало. А кодов на базе enum - очень много, и надо чтоб они хорошо выделялись из текста.
Если нет какой-либо корп. политики-правил, то это дело целесообразности-удобства для аффтора sm.gif
Я использую префикс eSET_MODE_RUN - с макросами уже не путаются.
IMHO.

sigmaN
Цитата
а почему Вы большими буквами значения енума определяете?
да это всё дело вкуса на самом деле... Это как раз не принципиально.
dxp
QUOTE (sigmaN @ May 26 2017, 22:45) *
да это всё дело вкуса на самом деле... Это как раз не принципиально.

Скорее не вкуса, а стиля. Заглавными символами принято описывать неизменяемые сущности типа констант, литералов.
CODE
#define SLON 1
const uint32_t MAMONT = 10;
enum TSlon
{
    KOT = 20,
    BEGEMOT
};

...

TSlon Slon = KOT;

uint32_t x = Slon == BEGEMOT ?  MAMONT : SLON;


Сразу видно, где переменные, а где константы/литералы. Устоявшийся стиль. Стараюсь придерживаться его.
sigmaN
Кстати вы заметили, как в С++11 поправили enum? Я имею ввиду enum class.
Убрали возможность неявного приведения к int, ограничили областть видимости перечислений?

Конечно же для совместимости им пришлось оставить старый вариант, но тем не менее это еще раз доказывает, что ход мыслей программиста на плюсах должен всегда по возможности двигаться в сторону более строгой типизации.
jcxz
Цитата(k155la3 @ May 26 2017, 12:01) *
Кроме очевидных преимуществ-удобств разгружающих моск.
Если Вы определили возвр. значение с типом enum, то при работе на отладке

Enum в аргументах/результатах функций это конечно правильно и удобно в отладке, но к сожалению например IAR при работе с enum-аргументами функции вставляет в каждой операции команды расширения байт->слово.
sigmaN
Цитата
The enum type
The compiler will use the smallest type required to hold enum constants, preferring
signed rather than unsigned.
When IAR Systems language extensions are enabled, and in C++, the enum constants
and types can also be of the type long, unsigned long, long long, or unsigned
long long.
To make the compiler use a larger type than it would automatically use, define an enum
constant with a large enough value. For example:
/* Disables usage of the char type for enum */
enum Cards{Spade1, Spade2,
DontUseChar=257};

http://supp.iar.com/FilesPublic/UPDINFO/00...opmentGuide.pdf page 227

dxp
QUOTE (sigmaN @ May 28 2017, 16:13) *
Кстати вы заметили, как в С++11 поправили enum? Я имею ввиду enum class.
Убрали возможность неявного приведения к int, ограничили областть видимости перечислений?

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

Да, улучшения налицо. Только enum class - это, я понял, как раз создание пространства имён для элементов перечисления, а тип там указывается, например, как enum EnumName : uint32_t (тип можно использовать любой целый, который подходит для хранения представления).

Жаль, что некоторые популярные компиляторы - например, IAR, - не поддерживают C++11. Или я отстал о жизни?
sigmaN
Цитата
Только enum class - это, я понял, как раз создание пространства имён для элементов перечисления
Ну и это и плюс отсутствие неявного преобразования в int.

Код
enum class Color {RED, GREEN, BLUE};  
int i = Color::RED; //compiler error!
int i = static_cast<int>(Color::RED); //Ok


Т.е. именно более сильная типизация во всех её проявлениях.
AlexRayne
Цитата(dxp @ May 27 2017, 09:21) *
Скорее не вкуса, а стиля. Заглавными символами принято описывать неизменяемые сущности типа констант, литералов.

Сразу видно, где переменные, а где константы/литералы. Устоявшийся стиль. Стараюсь придерживаться его.


На моем опыте старались отличать макрос от немакроса.
а вот как раз константы вполне подходит описывать как и обычные переменные, потому что на одном этапе разработки они переменные, на другом уже константы.
jcxz
Цитата(sigmaN @ May 28 2017, 13:17) *
The compiler will use the smallest type required to hold enum constants, preferring

Это всё известно и хорошо. Но, по уму, должно касаться только правил хранения этого enum. Т.е. - какого размера память выделить для его хранения. А не касательно аргументов функций передаваемых в регистрах. При передаче аргумента в регистре, он занимает весь регистр, и расширение байт->слово в таком случае - совершенно бессмысленная операция. Да ещё выполняемая перед каждым использованием значения этого enum-а.
Конечно можно сделать: int x = x_enum; в начале функции, но опять теряется удобство отладки sad.gif
sigmaN
Ну тогда надо конкретизировать на примерах.
Я так понимаю, что если для энума(для его хранения как вы говорите) будет выбран тип uint8_t то и аргумент функции тоже будет uint8_t и передача аргумента этой функции должна быть выполнена по тем-же правилам, что и передача аргумента с типом uint8_t. Как-то слишком уж глупо нечто, что хранится как uint8_t передавать в функцию по правилам uint16_t...

Конкретизируем.
STM8, IAR, функция с одинм аргументом.
По правилам передачи аргументов 8ми битные передаются через регистр А, 16ти битные через регистр X
Вы утрерждаете, что enum будет передан через регистр X не смотря на то, что в памяти будет занимать 8бит.
Правильно я понял ход ваших мыслей?

Код
typedef enum{
  TEST1,
  TEST2
} testEnum_t;

volatile testEnum_t te = TEST1; //to avoid inlining and optimizations

void testF(testEnum_t enm)
{
  nop();
    
  switch(enm)
  {
  case TEST1: GPIOA->ODR = 0;
    break;
  case TEST2: GPIOA->ODR = 0xff;
    break;
  }
  nop();
}



int main()
{
  testF(te);
}


Код
   327            testF(te);
   \   000008 C6 ....      LD        A, L:te
   \   00000B 9D           NOP
   \   00000C 27 09        JREQ      L:??main_0
   \   00000E 4A           DEC       A
   \   00000F 26 0A        JRNE      L:??main_1
   \   000011 35 FF 5000   MOV       L:0x5000, #0xff
   \   000015 20 04        JRA       L:??main_1
   \                     ??main_0:
   \   000017 725F 5000    CLR       L:0x5000
   \                     ??main_1:
   \   00001B 9D           NOP


Всё замечательно, аргумент функции в виде энума использует 8ми битный регистр A ибо весь энум помещается в 8бит.

исправляем так, чтоб энум стал 16бит. Всё остальное не меняем
Код
typedef enum{
  TEST1,
  TEST2 = 1024
} testEnum_t;

компилируем....
Код
    327            testF(te);
   \   000008 CE ....      LDW       X, L:te
   \   00000B 9D           NOP
   \   00000C 27 0B        JREQ      L:??main_0
   \   00000E 1D 0400      SUBW      X, #0x400
   \   000011 26 0A        JRNE      L:??main_1
   \   000013 35 FF 5000   MOV       L:0x5000, #0xff
   \   000017 20 04        JRA       L:??main_1
   \                     ??main_0:
   \   000019 725F 5000    CLR       L:0x5000
   \                     ??main_1:
   \   00001D 9D           NOP

используется регистр X.
jcxz
Цитата(sigmaN @ May 29 2017, 10:55) *
Я так понимаю, что если для энума(для его хранения как вы говорите) будет выбран тип uint8_t то и аргумент функции тоже будет uint8_t и передача аргумента этой функции должна быть выполнена по тем-же правилам, что и передача аргумента с типом uint8_t. Как-то слишком уж глупо нечто, что хранится как uint8_t передавать в функцию по правилам uint16_t...

Глупо - это если сказано про расширение размера данных не понимать, что речь естественно идёт не о 8-битной архитектуре. Я говорил про 32-битный IAR for ARM.
И я написал: аргумент функции типа enum, а не u8 или u16.
sigmaN
Вот и я гворю: уточнять надо. Кто куда и кого расширяет.

Пока, кстати, не видно разницы между аргументами i8 i16 i32 и энумами с соответствующими размерами.... Но IAR для ARM у меня под рукой нет.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2024 Invision Power Services, Inc.