Related
I have a set of defined macros as follows.
#define ARM_FRONT_REG 1
..............
#define ARM_REAR_REG 10
#define MOTOR_MAIN_REG 1
..............
#define MOTOR_AUX_REG 3
#define MOTOR_REGISTER_ADDRESS(register_offset) \
( \
addr = MOTOR_BASE_ADDR * (1 << BITS_PER_MODULE) + register_offset) \
) \
#define ARM_REGISTER_ADDRESS(register_offset) \
( \
addr = ARM_BASE_ADDR * (1 << BITS_PER_MODULE) + register_offset) \
) \
I am using macros like
ui_address = ARM_BASE_ADDR (ARM_REAR_REG)
ui_address = MOTOR_REGISTER_ADDRESS (MOTOR_MAIN_REG)
I want to restrict macro usage which is mixed with each other. Is there a way of aborting compiling if macros used as following?
ui_address = ARM_BASE_ADDR (MOTOR_MAIN_REG)
ui_address = MOTOR_REGISTER_ADDRESS (ARM_REAR_REG)
PS :
I have mentioned macros in brief, But actual macros are as below, which used to perform register reads write to Linux driver from user application.
actual struct :
struct hw_register_struct
{
int log_level;
unsigned int reg_addr;
unsigned int reg_value;
char reg_name [MAX_REG_NAME_LENGTH];
char application_info [APP_INFO_LENGTH];
};
This macro validates the address is correct per module.
#define CHECK_ADDR_SUB_MODULE(module_index, sub_module, sub_module_bits, offset, max_reg_count)
({
unsigned int check_ret = 0;
if(offset >= max_reg_count){
hw_register.reg_addr = 0;
check_ret = 1;
} else {
hw_register.reg_addr = (module_index * (1 << BITS_PER_MODULE) + (1 << sub_module_bits) * (sub_module) + offset);
}
check_ret;
})
This macro assigns the address to the variable in the struct.
#define SEQUENCER_REGISTER_ADDRESS(register_offset)
({
memset((void *)&hw_register, 0, sizeof(struct hw_register_struct));
if(CHECK_ADDR_SUB_MODULE(MODULE_SEQUENCER, 0, register_offset, SEQ_REG_COUNT)){
Logger::Print(ERROR_LEVEL, "Invalid Address | Module : %s | Address : %s", STR(MODULE_SEQUENCER), #register_offset);
}
memcpy(hw_register.reg_name, #register_offset, sizeof(#register_offset));
hw_register.reg_addr;
})
Perform calling the ioctl to Linux driver
#define WRITE_REGISTER_(register_addr, register_value, func, line, log_level_)
{
register_addr;
hw_register.reg_value = register_value;
hw_register.log_level = log_level_;
snprintf(hw_register.application_info, APP_INFO_LENGTH - 1,"%s:%d", func, line);
long ret_ioctl = p_IOCTL->IOCTL<struct hw_register_struct>(IOCTL_WRITE, hw_register);
if(unlikely(ret_ioctl != 0))
{
Logger::Print(ERROR_LEVEL, "IOCTL WRITE_REGISTER Failed | Reg: %u, Reg Name [ %s ]", hw_register.reg_addr, hw_register.reg_name);
}
}
#define WRITE_REGISTER_INFO(register_addr, register_value) WRITE_REGISTER_(register_addr, register_value, __func__, __LINE__, KERN_INFO_LEVEL)
In your case, one thing you can do is have the macros taking arguments adding a name prefix to the argument passed. E.g.:
#define ARM_REGISTER_ADDRESS(register_offset) \
( \
addr = ARM_BASE_ADDR * (1 << BITS_PER_MODULE) + ARM_##register_offset) \
)
The ## will concatenate ARM_ and the argument passed to the macro. Then you can use it as:
ui_address = ARM_BASE_ADDR (REAR_REG);
And
ui_address = ARM_BASE_ADDR (MAIN_REG);
Would fail because ARM_MAIN_REG doesn't exist (in your case).
But I don't think typechecking even using enums will solve your issue (at least, I am not aware of a compiler option to allow it).
If you need to do it in preprocessor, you could use a tag with the variables and then concatenate it with a unique name that will expand to the result, something along:
#define MOTOR_BASE_ADDR 1
#define BITS_PER_MODULE 2
#define ARM_FRONT_REG (ARM, 1)
#define ARM_REAR_REG (ARM, 10)
#define MOTOR_MAIN_REG (MOTOR, 1)
#define MOTOR_AUX_REG (MOTOR, 3)
#define MOTOR_UNIQUE_STRING(x) x
#define MOTOR_ONLY(a, b) a##_UNIQUE_STRING(b)
#define MOTOR_REGISTER_ADDRESS(register_offset) \
( MOTOR_BASE_ADDR * (1 << BITS_PER_MODULE) + MOTOR_ONLY register_offset )
#define ARM_UNIQUE_STRING_FDASDFSAFDA(x) x
#define ARM_ONLY(a, b) a##_UNIQUE_STRING_FDASDFSAFDA(b)
#define ARM_REGISTER_ADDRESS(register_offset) \
( ARM_BASE_ADDR * (1 << BITS_PER_MODULE) + ARM_ONLY register_offset )
int main() {
MOTOR_REGISTER_ADDRESS(MOTOR_MAIN_REG); // all fine
MOTOR_REGISTER_ADDRESS(ARM_FRONT_REG); // error - sytnax error or ARM_UNIQUE_STRING undeclared
}
Picking a unique tag name and unique string will essentially work as a protecting against strange names. You could pick more unique name for the functions.
You could use actual proper types and use an actual function depending that one structure type can't be converted to another:
struct arm_register { long v; };
static const struct arm_register ARM_FRONT_REG = {1};
struct motor_register { long v; };
static const struct motor_register MOTOR_MAIN_REG = {1};
#define MOTOR_BASE_ADDR 1
#define BITS_PER_MODULE 2
static inline long MOTOR_REGISTER_ADDRESS(struct motor_register register_offset) {
return MOTOR_BASE_ADDR * (1 << BITS_PER_MODULE) + register_offset.v;
}
int main() {
MOTOR_REGISTER_ADDRESS(MOTOR_MAIN_REG); // all fine
MOTOR_REGISTER_ADDRESS(ARM_FRONT_REG); // error - incompatible type
}
# or with _Generic:
#define MOTOR_REGISTER_ADDRESS_2(x) \
_Generic((x), struct motor_register: MOTOR_BASE_ADDR * (1 << BITS_PER_MODULE) + x.v)
static const int a = MOTOR_REGISTER_ADDRESS_2(MOTOR_MAIN_REG); // all fine
static const int b = MOTOR_REGISTER_ADDRESS_2(ARM_FRONT_REG); // error - _Generic can't be chosen
int main()
{
enum Days{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday};
Days TheDay;
int j = 0;
printf("Please enter the day of the week (0 to 6)\n");
scanf("%d",&j);
TheDay = Days(j);
//how to PRINT THE VALUES stored in TheDay
printf("%s",TheDay); // isnt working
return 0;
}
Enumerations in C are numbers that have convenient names inside your code. They are not strings, and the names assigned to them in the source code are not compiled into your program, and so they are not accessible at runtime.
The only way to get what you want is to write a function yourself that translates the enumeration value into a string. E.g. (assuming here that you move the declaration of enum Days outside of main):
const char* getDayName(enum Days day)
{
switch (day)
{
case Sunday: return "Sunday";
case Monday: return "Monday";
/* etc... */
}
}
/* Then, later in main: */
printf("%s", getDayName(TheDay));
Alternatively, you could use an array as a map, e.g.
const char* dayNames[] = {"Sunday", "Monday", "Tuesday", /* ... etc ... */ };
/* ... */
printf("%s", dayNames[TheDay]);
But here you would probably want to assign Sunday = 0 in the enumeration to be safe... I'm not sure if the C standard requires compilers to begin enumerations from 0, although most do (I'm sure someone will comment to confirm or deny this).
I use something like this:
in a file "EnumToString.h":
#undef DECL_ENUM_ELEMENT
#undef DECL_ENUM_ELEMENT_VAL
#undef DECL_ENUM_ELEMENT_STR
#undef DECL_ENUM_ELEMENT_VAL_STR
#undef BEGIN_ENUM
#undef END_ENUM
#ifndef GENERATE_ENUM_STRINGS
#define DECL_ENUM_ELEMENT( element ) element,
#define DECL_ENUM_ELEMENT_VAL( element, value ) element = value,
#define DECL_ENUM_ELEMENT_STR( element, descr ) DECL_ENUM_ELEMENT( element )
#define DECL_ENUM_ELEMENT_VAL_STR( element, value, descr ) DECL_ENUM_ELEMENT_VAL( element, value )
#define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
#define END_ENUM( ENUM_NAME ) ENUM_NAME; \
const char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
#define BEGIN_ENUM( ENUM_NAME) const char * GetString##ENUM_NAME( enum tag##ENUM_NAME index ) {\
switch( index ) {
#define DECL_ENUM_ELEMENT( element ) case element: return #element; break;
#define DECL_ENUM_ELEMENT_VAL( element, value ) DECL_ENUM_ELEMENT( element )
#define DECL_ENUM_ELEMENT_STR( element, descr ) case element: return descr; break;
#define DECL_ENUM_ELEMENT_VAL_STR( element, value, descr ) DECL_ENUM_ELEMENT_STR( element, descr )
#define END_ENUM( ENUM_NAME ) default: return "Unknown value"; } } ;
#endif
then in any header file you make the enum declaration, day enum.h
#include "EnumToString.h"
BEGIN_ENUM(Days)
{
DECL_ENUM_ELEMENT(Sunday) //will render "Sunday"
DECL_ENUM_ELEMENT(Monday) //will render "Monday"
DECL_ENUM_ELEMENT_STR(Tuesday, "Tuesday string") //will render "Tuesday string"
DECL_ENUM_ELEMENT(Wednesday) //will render "Wednesday"
DECL_ENUM_ELEMENT_VAL_STR(Thursday, 500, "Thursday string") // will render "Thursday string" and the enum will have 500 as value
/* ... and so on */
}
END_ENUM(MyEnum)
then in a file called EnumToString.c:
#include "enum.h"
#define GENERATE_ENUM_STRINGS // Start string generation
#include "enum.h"
#undef GENERATE_ENUM_STRINGS // Stop string generation
then in main.c:
int main(int argc, char* argv[])
{
Days TheDay = Monday;
printf( "%d - %s\n", TheDay, GetStringDay(TheDay) ); //will print "1 - Monday"
TheDay = Thursday;
printf( "%d - %s\n", TheDay, GetStringDay(TheDay) ); //will print "500 - Thursday string"
return 0;
}
this will generate "automatically" the strings for any enums declared this way and included in "EnumToString.c"
The way I usually do this is by storing the string representations in a separate array in the same order, then indexing the array with the enum value:
const char *DayNames[] = { "Sunday", "Monday", "Tuesday", /* etc */ };
printf("%s", DayNames[Sunday]); // prints "Sunday"
enums in C don't really work the way you're expecting them to. You can think of them kind of like glorified constants (with a few additional benefits relating to being a collection of such constants), and the text you've written in for "Sunday" really gets resolved to a number during compilation, the text is ultimately discarded.
In short: to do what you really want you'll need to keep an array of the strings or create a function to map from the enum's value to the text you'd like to print.
Enumerations in C are basically syntactical sugar for named lists of automatically-sequenced integer values. That is, when you have this code:
int main()
{
enum Days{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday};
Days TheDay = Monday;
}
Your compiler actually spits out this:
int main()
{
int TheDay = 1; // Monday is the second enumeration, hence 1. Sunday would be 0.
}
Therefore, outputting a C enumeration as a string is not an operation that makes sense to the compiler. If you want to have human-readable strings for these, you will need to define functions to convert from enumerations to strings.
Here's a cleaner way to do it with macros:
#include <stdio.h>
#include <stdlib.h>
#define DOW(X, S) \
X(Sunday) S X(Monday) S X(Tuesday) S X(Wednesday) S X(Thursday) S X(Friday) S X(Saturday)
#define COMMA ,
/* declare the enum */
#define DOW_ENUM(DOW) DOW
enum dow {
DOW(DOW_ENUM, COMMA)
};
/* create an array of strings with the enum names... */
#define DOW_ARR(DOW ) [DOW] = #DOW
const char * const dow_str[] = {
DOW(DOW_ARR, COMMA)
};
/* ...or create a switchy function. */
static const char * dowstr(int i)
{
#define DOW_CASE(D) case D: return #D
switch(i) {
DOW(DOW_CASE, ;);
default: return NULL;
}
}
int main(void)
{
for(int i = 0; i < 7; i++)
printf("[%d] = «%s»\n", i, dow_str[i]);
printf("\n");
for(int i = 0; i < 7; i++)
printf("[%d] = «%s»\n", i, dowstr(i));
return 0;
}
I'm not sure that this is totally portable b/w preprocessors, but it works with gcc.
This is c99 btw, so use c99 strict if you plug it into (the online compiler) ideone.
I know I am late to the party, but how about this?
const char* dayNames[] = { [Sunday] = "Sunday", [Monday] = "Monday", /*and so on*/ };
printf("%s", dayNames[Sunday]); // prints "Sunday"
This way, you do not have to manually keep the enum and the char* array in sync. If you are like me, chances are that you will later change the enum, and the char* array will print invalid strings.
This may not be a feature universally supported. But afaik, most of the mordern day C compilers support this designated initialier style.
You can read more about designated initializers here.
I like this to have enum in the dayNames.
To reduce typing, we can do the following:
#define EP(x) [x] = #x /* ENUM PRINT */
const char* dayNames[] = { EP(Sunday), EP(Monday)};
The question is you want write the name just one times.
I have an ider like this:
#define __ENUM(situation,num) \
int situation = num; const char * __##situation##_name = #situation;
const struct {
__ENUM(get_other_string, -203);//using a __ENUM Mirco make it ease to write,
__ENUM(get_negative_to_unsigned, -204);
__ENUM(overflow,-205);
//The following two line showing the expanding for __ENUM
int get_no_num = -201; const char * __get_no_num_name = "get_no_num";
int get_float_to_int = -202; const char * get_float_to_int_name = "float_to_int_name";
}eRevJson;
#undef __ENUM
struct sIntCharPtr { int value; const char * p_name; };
//This function transform it to string.
inline const char * enumRevJsonGetString(int num) {
sIntCharPtr * ptr = (sIntCharPtr *)(&eRevJson);
for (int i = 0;i < sizeof(eRevJson) / sizeof(sIntCharPtr);i++) {
if (ptr[i].value == num) {
return ptr[i].p_name;
}
}
return "bad_enum_value";
}
it uses a struct to insert enum, so that a printer to string could follows each enum value define.
int main(int argc, char *argv[]) {
int enum_test = eRevJson.get_other_string;
printf("error is %s, number is %d\n", enumRevJsonGetString(enum_test), enum_test);
>error is get_other_string, number is -203
The difference to enum is builder can not report error if the numbers are repeated.
if you don't like write number, __LINE__ could replace it:
#define ____LINE__ __LINE__
#define __ENUM(situation) \
int situation = (____LINE__ - __BASELINE -2); const char * __##situation##_name = #situation;
constexpr int __BASELINE = __LINE__;
constexpr struct {
__ENUM(Sunday);
__ENUM(Monday);
__ENUM(Tuesday);
__ENUM(Wednesday);
__ENUM(Thursday);
__ENUM(Friday);
__ENUM(Saturday);
}eDays;
#undef __ENUM
inline const char * enumDaysGetString(int num) {
sIntCharPtr * ptr = (sIntCharPtr *)(&eDays);
for (int i = 0;i < sizeof(eDays) / sizeof(sIntCharPtr);i++) {
if (ptr[i].value == num) {
return ptr[i].p_name;
}
}
return "bad_enum_value";
}
int main(int argc, char *argv[]) {
int d = eDays.Wednesday;
printf("day %s, number is %d\n", enumDaysGetString(d), d);
d = 1;
printf("day %s, number is %d\n", enumDaysGetString(d), d);
}
>day Wednesday, number is 3 >day Monday, number is 1
There is another solution: Create your own dynamic enumeration class. Means you have a struct and some function to create a new enumeration, which stores the elements in a struct and each element has a string for the name. You also need some type to store a individual elements, functions to compare them and so on.
Here is an example:
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Enumeration_element_T
{
size_t index;
struct Enumeration_T *parent;
char *name;
};
struct Enumeration_T
{
size_t len;
struct Enumeration_element_T elements[];
};
void enumeration_delete(struct Enumeration_T *self)
{
if(self)
{
while(self->len--)
{
free(self->elements[self->len].name);
}
free(self);
}
}
struct Enumeration_T *enumeration_create(size_t len,...)
{
//We do not check for size_t overflows, but we should.
struct Enumeration_T *self=malloc(sizeof(self)+sizeof(self->elements[0])*len);
if(!self)
{
return NULL;
}
self->len=0;
va_list l;
va_start(l,len);
for(size_t i=0;i<len;i++)
{
const char *name=va_arg(l,const char *);
self->elements[i].name=malloc(strlen(name)+1);
if(!self->elements[i].name)
{
enumeration_delete(self);
return NULL;
}
strcpy(self->elements[i].name,name);
self->len++;
}
return self;
}
bool enumeration_isEqual(struct Enumeration_element_T *a,struct Enumeration_element_T *b)
{
return a->parent==b->parent && a->index==b->index;
}
bool enumeration_isName(struct Enumeration_element_T *a, const char *name)
{
return !strcmp(a->name,name);
}
const char *enumeration_getName(struct Enumeration_element_T *a)
{
return a->name;
}
struct Enumeration_element_T *enumeration_getFromName(struct Enumeration_T *self, const char *name)
{
for(size_t i=0;i<self->len;i++)
{
if(enumeration_isName(&self->elements[i],name))
{
return &self->elements[i];
}
}
return NULL;
}
struct Enumeration_element_T *enumeration_get(struct Enumeration_T *self, size_t index)
{
return &self->elements[index];
}
size_t enumeration_getCount(struct Enumeration_T *self)
{
return self->len;
}
bool enumeration_isInRange(struct Enumeration_T *self, size_t index)
{
return index<self->len;
}
int main(void)
{
struct Enumeration_T *weekdays=enumeration_create(7,"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday");
if(!weekdays)
{
return 1;
}
printf("Please enter the day of the week (0 to 6)\n");
size_t j = 0;
if(scanf("%zu",&j)!=1)
{
enumeration_delete(weekdays);
return 1;
}
// j=j%enumeration_getCount(weekdays); //alternative way to make sure j is in range
if(!enumeration_isInRange(weekdays,j))
{
enumeration_delete(weekdays);
return 1;
}
struct Enumeration_element_T *day=enumeration_get(weekdays,j);
printf("%s\n",enumeration_getName(day));
enumeration_delete(weekdays);
return 0;
}
The functions of enumeration should be in their own translation unit, but i combined them here to make it simpler.
The advantage is that this solution is flexible, follows the DRY principle, you can store information along with each element, you can create new enumerations during runtime and you can add new elements during runtime.
The disadvantage is that this is complex, needs dynamic memory allocation, can't be used in switch-case, needs more memory and is slower. The question is if you should not use a higher level language in cases where you need this.
Using a Macro and stringize operator(#) we can achieve this....
#include <stdio.h>
typedef enum
{
MON=0,
TUE
}week;
int main()
{
#define printt(data) printf("%s",#data);
printt(MON);
return 0;
}
i'm new to this but a switch statement will defenitely work
#include <stdio.h>
enum mycolor;
int main(int argc, const char * argv[])
{
enum Days{Sunday=1,Monday=2,Tuesday=3,Wednesday=4,Thursday=5,Friday=6,Saturday=7};
enum Days TheDay;
printf("Please enter the day of the week (0 to 6)\n");
scanf("%d",&TheDay);
switch (TheDay)
{
case Sunday:
printf("the selected day is sunday");
break;
case Monday:
printf("the selected day is monday");
break;
case Tuesday:
printf("the selected day is Tuesday");
break;
case Wednesday:
printf("the selected day is Wednesday");
break;
case Thursday:
printf("the selected day is thursday");
break;
case Friday:
printf("the selected day is friday");
break;
case Saturday:
printf("the selected day is Saturaday");
break;
default:
break;
}
return 0;
}
TheDay maps back to some integer type. So:
printf("%s", TheDay);
Attempts to parse TheDay as a string, and will either print out garbage or crash.
printf is not typesafe and trusts you to pass the right value to it. To print out the name of the value, you'd need to create some method for mapping the enum value to a string - either a lookup table, giant switch statement, etc.
I have code in Code Reveiw that "works" as expected, yet may have UB
.
Code has an array of same-sized char arrays called GP2_format[]. To detect if the pointer format has the same value as the address of one of the elements GP2_format[][0], the below code simple tested if the pointer was >= the smallest element and <= the greatest. As the elements are size 1, no further checking needed.
const char GP2_format[GP2_format_N + 1][1];
const char *format = ...;
if (format >= GP2_format[0] && format <= GP2_format[GP2_format_N]) Inside()
else Outside();
C11 §6.5.8/5 Relational operators < > <= >= appears to define this as the dreaded Undefined Behavior when comparing a pointer from outside the array.
When two pointers are compared, the result depends on the relative locations in the address space of the objects pointed to. If two pointers to object types both point to the
same object, ... of the same array object, they compare equal. ...(same object OK) .... (same union OK) .... (same array OK) ... In all other cases, the behavior is undefined.
Q1 Is code's pointer compare in GP2_get_type() UB?
Q2 If so, what is a well defined alternate, search O(1), to the questionable GP2_get_type()?
Slower solutions
Code could sequentially test format against each GP2_format[] or convert the values to intptr_t, sort one time and do a O(ln2(n)) search.
Similar
...if a pointer is part of a set, but this "set" is not random, it is an array.
intptr_t approach - maybe UB.
#include <stdio.h>
typedef enum {
GP2_set_precision,
GP2_set_w,
GP2_setios_flags_,
GP2_string_,
GP2_unknown_,
GP2_format_N
} GP2_type;
const char GP2_format[GP2_format_N + 1][1];
static int GP2_get_type(const char *format) {
// candidate UB with pointer compare
if (format >= GP2_format[0] && format <= GP2_format[GP2_format_N]) {
return (int) (format - GP2_format[0]);
}
return GP2_format_N;
}
int main(void) {
printf("%d\n", GP2_get_type(GP2_format[1]));
printf("%d\n", GP2_get_type("Hello World")); // potential UB
return 0;
}
Output (as expected, yet potentially UB)
1
5
If you want to comply with the C Standard then your options are:
Perform individual == or != tests against each pointer in the target range
You could use a hash table or search tree or something to speed this up, if it is a very large set
Redesign your code to not require this check.
A "probably works" method would be to cast all of the values to uintptr_t and then do relational comparison. If the system has a memory model with absolute ordering then it should define uintptr_t and preserve that ordering; and if it doesn't have such a model then the relational compare idea never would have worked anyway.
This is not an answer to the stated question, but an answer to the underlying problem.
Unless I am mistaken, the entire problem can be avoided by making GP_format a string. This way the problem simplifies to checking whether a pointer points to within a known string, and that is not UB. (If it is, then using strchr() to find a character and compute its index in the string would be UB, which would be completely silly. That would be a serious bug in the standard, in my opinion. Then again, I'm not a language lawyer, just a programmer that tries to write robust, portable C. Fortunately, the standard states it's written to help people like me, and not compiler writers who want to avoid doing hard work by generating garbage whenever a technicality in the standard lets them.)
Here is a full example of the approach I had in mind. This also compiles with clang-3.5, since the newest GCC I have on the machine I'm currently using is version 4.8.4, which has no _Generic() support. If you use a different version of clang, or gcc, change the first line in the Makefile accordingly, or run e.g. make CC=gcc.
First, Makefile:
CC := clang-3.5
CFLAGS := -Wall -Wextra -std=c11 -O2
LD := $(CC)
LDFLAGS :=
PROGS := example
.PHONY: all clean
all: clean $(PROGS)
clean:
rm -f *.o $(PROGS)
%.o: %.c
$(CC) $(CFLAGS) -c $^
example: out.o main.o
$(LD) $^ $(LDFLAGS) -o $#
Next, out.h:
#ifndef OUT_H
#define OUT_H 1
#include <stdio.h>
typedef enum {
out_char,
out_int,
out_double,
out_FILE,
out_set_fixed,
out_set_width,
out_set_decimals,
out_count
} out_type;
extern const char out_formats[out_count + 1];
extern int outf(FILE *, ...);
#define out(x...) outf(stdout, x)
#define err(x...) outf(stderr, x)
#define OUT(x) _Generic( (x), \
FILE *: out_formats + out_FILE, \
double: out_formats + out_double, \
int: out_formats + out_int, \
char: out_formats + out_char ), (x)
#define OUT_END ((const char *)0)
#define OUT_EOL "\n", ((const char *)0)
#define OUT_fixed(x) (out_formats + out_set_fixed), ((int)(x))
#define OUT_width(x) (out_formats + out_set_width), ((int)(x))
#define OUT_decimals(x) (out_formats + out_set_decimals), ((int)(x))
#endif /* OUT_H */
Note that the above OUT() macro expands to two subexpressions separated by a comma. The first subexpression uses _Generic() to emit a pointer within out_formats based on the type of the macro argument. The second subexpression is the macro argument itself.
Having the first argument to the outf() function be a fixed one (the initial stream to use) simplifies the function implementation quite a bit.
Next, out.c:
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include "out.h"
/* out_formats is a string consisting of ASCII NULs,
* i.e. an array of zero chars.
* We only check if a char pointer points to within out_formats,
* if it points to a zero char; otherwise, it's just a normal
* string we print as-is.
*/
const char out_formats[out_count + 1] = { 0 };
int outf(FILE *out, ...)
{
va_list args;
int fixed = 0;
int width = -1;
int decimals = -1;
if (!out)
return EINVAL;
va_start(args, out);
while (1) {
const char *const format = va_arg(args, const char *);
if (!format) {
va_end(args);
return 0;
}
if (*format) {
if (fputs(format, out) == EOF) {
va_end(args);
return 0;
}
} else
if (format >= out_formats && format < out_formats + sizeof out_formats) {
switch ((out_type)(format - out_formats)) {
case out_char:
if (fprintf(out, "%c", va_arg(args, int)) < 0) {
va_end(args);
return EIO;
}
break;
case out_int:
if (fprintf(out, "%*d", width, (int)va_arg(args, int)) < 0) {
va_end(args);
return EIO;
}
break;
case out_double:
if (fprintf(out, fixed ? "%*.*f" : "%*.*e", width, decimals, (float)va_arg(args, double)) < 0) {
va_end(args);
return EIO;
}
break;
case out_FILE:
out = va_arg(args, FILE *);
if (!out) {
va_end(args);
return EINVAL;
}
break;
case out_set_fixed:
fixed = !!va_arg(args, int);
break;
case out_set_width:
width = va_arg(args, int);
break;
case out_set_decimals:
decimals = va_arg(args, int);
break;
case out_count:
break;
}
}
}
}
Note that the above lacks even OUT("string literal") support; it's quite minimal implementation.
Finally, the main.c to show an example of using the above:
#include <stdlib.h>
#include "out.h"
int main(void)
{
double q = 1.0e6 / 7.0;
int x;
out("Hello, world!\n", OUT_END);
out("One seventh of a million is ", OUT_decimals(3), OUT(q), " = ", OUT_fixed(1), OUT(q), ".", OUT_EOL);
for (x = 1; x <= 9; x++)
out(OUT(stderr), OUT(x), " ", OUT_width(2), OUT(x*x), OUT_EOL);
return EXIT_SUCCESS;
}
In a comment, chux pointed out that we can get rid of the pointer inequality comparisons, if we fill the out_formats array; then (assuming, just for paranoia's sake, we skip the zero index), we can use (*format > 0 && *format < out_type_max && format == out_formats + *format) for the check. This seems to work just fine.
I also applied Pascal Cuoq's answer on how to make string literals decay into char * for _Generic(), so this does support out(OUT("literal")). Here is the modified out.h:
#ifndef OUT_H
#define OUT_H 1
#include <stdio.h>
typedef enum {
out_string = 1,
out_int,
out_double,
out_set_FILE,
out_set_fixed,
out_set_width,
out_set_decimals,
out_type_max
} out_type;
extern const char out_formats[out_type_max + 1];
extern int outf(FILE *, ...);
#define out(x...) outf(stdout, x)
#define err(x...) outf(stderr, x)
#define OUT(x) _Generic( (0,x), \
FILE *: out_formats + out_set_FILE, \
double: out_formats + out_double, \
int: out_formats + out_int, \
char *: out_formats + out_string ), (x)
#define OUT_END ((const char *)0)
#define OUT_EOL "\n", ((const char *)0)
#define OUT_fixed(x) (out_formats + out_set_fixed), ((int)(x))
#define OUT_width(x) (out_formats + out_set_width), ((int)(x))
#define OUT_decimals(x) (out_formats + out_set_decimals), ((int)(x))
#endif /* OUT_H */
Here is the correspondingly modified implementation, out.c:
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include "out.h"
const char out_formats[out_type_max + 1] = {
[ out_string ] = out_string,
[ out_int ] = out_int,
[ out_double ] = out_double,
[ out_set_FILE ] = out_set_FILE,
[ out_set_fixed ] = out_set_fixed,
[ out_set_width ] = out_set_width,
[ out_set_decimals ] = out_set_decimals,
};
int outf(FILE *stream, ...)
{
va_list args;
/* State (also, stream is included in state) */
int fixed = 0;
int width = -1;
int decimals = -1;
va_start(args, stream);
while (1) {
const char *const format = va_arg(args, const char *);
if (!format) {
va_end(args);
return 0;
}
if (*format > 0 && *format < out_type_max && format == out_formats + (size_t)(*format)) {
switch ((out_type)(*format)) {
case out_string:
{
const char *s = va_arg(args, char *);
if (s && *s) {
if (!stream) {
va_end(args);
return EINVAL;
}
if (fputs(s, stream) == EOF) {
va_end(args);
return EINVAL;
}
}
}
break;
case out_int:
if (!stream) {
va_end(args);
return EINVAL;
}
if (fprintf(stream, "%*d", width, (int)va_arg(args, int)) < 0) {
va_end(args);
return EIO;
}
break;
case out_double:
if (!stream) {
va_end(args);
return EINVAL;
}
if (fprintf(stream, fixed ? "%*.*f" : "%*.*e", width, decimals, va_arg(args, double)) < 0) {
va_end(args);
return EIO;
}
break;
case out_set_FILE:
stream = va_arg(args, FILE *);
if (!stream) {
va_end(args);
return EINVAL;
}
break;
case out_set_fixed:
fixed = !!va_arg(args, int);
break;
case out_set_width:
width = va_arg(args, int);
break;
case out_set_decimals:
decimals = va_arg(args, int);
break;
case out_type_max:
/* This is a bug. */
break;
}
} else
if (*format) {
if (!stream) {
va_end(args);
return EINVAL;
}
if (fputs(format, stream) == EOF) {
va_end(args);
return EIO;
}
}
}
}
If you find a bug or have a suggestion, please let me know in the comments. I don't actually need such code for anything, but I do find the approach very interesting.
I wonder what would be the best way to map Error codes to strings for instance in the SCSI protocol the errors are always returned as numbers. like this:
00h GOOD
02h CHECK CONDITION
04h CONDITION MET
08h BUSY
18h RESERVATION CONFLICT
28h TASK SET FULL
30h ACA ACTIVE
40h TASK ABORTED
I wonder what would be the best way to translate these codes on the fly to there string counterpart? I thought about doing an array or something but Ig would have to make an array of length 40h even though I wont use most of the indices. So is the best way to just make a function which takes the number as input and returns a string?
That's a bit tricky but you can try to use a X-macro.
#include <stdio.h>
#define SCSI_ERR_TABLE \
X(GOOD, "good", 0x00) \
X(CHECK_CONDITION, "check condition", 0x02) \
X(CONDITION_MET, "condition met", 0x04) \
X(BUSY, "busy", 0x08) \
#define X(ERR, STR, ID) ERR = ID,
enum {
SCSI_ERR_TABLE
};
#undef X
#define X(ERR, STR, ID) {.id = ID, .string = STR},
static const struct {
int id;
char string[32];
} scsi_err_strings[] = { SCSI_ERR_TABLE };
#undef X
const char *scsi_get_err_string(int error)
{
for (int i = 0; i < sizeof(scsi_err_strings) / sizeof(scsi_err_strings[0]); i++) {
if (scsi_err_strings[i].id == error)
return scsi_err_strings[i].string;
}
return NULL;
}
int main(void)
{
int err;
err = CHECK_CONDITION;
printf("%d -> %s\n", err, scsi_get_err_string(err));
err = BUSY;
printf("%d -> %s\n", err, scsi_get_err_string(err));
return 0;
}
Here's an example on how to define some codes as macros and then using another macro function to get the string value of the macro:
#include <stdio.h>
#define GOOD 0
#define CHECK_CONDITON 2
#define CONDITION_MET 4
/* more codes here */
#define STR_VALUE(CODE) #CODE
void print_scsi(int code) {
char *scsi_string;
switch (code) {
case GOOD:
scsi_string = STR_VALUE(GOOD); break;
case CHECK_CONDITON:
scsi_string = STR_VALUE(CHECK_CONDITON); break;
case CONDITION_MET:
scsi_string = STR_VALUE(CONDITION_MET); break;
/* ... */
}
printf("code: %d string: %s\n", code, scsi_string);
}
int main(void) {
print_scsi(0);
print_scsi(2);
print_scsi(4);
}
Running this gives:
code: 0 string: GOOD
code: 2 string: CHECK_CONDITON
code: 4 string: CONDITION_MET
If the list of codes is long, define the macros in a separate header file, along with STR_VALUE and include it your main program.
I need to switch based on a 4-character string. I put the string in a union so I can at least refer to it as a 32-bit integer.
union
{
int32u integer;
char string[4];
}software_version;
But now I don't know what to write in the case statements. I need some kind of macro to convert a 4-character string literal into the integer. E.G.
#define STRING_TO_INTEGER(s) ?? What goes here ??
#define VERSION_2_3_7 STRING_TO_INTEGER("0237")
#define VERSION_2_4_1 STRING_TO_INTEGER("0241")
switch (array[i].software_version.integer)
{
case VERSION_2_3_7:
break;
case VERSION_2_4_1:
break;
}
Is there a way to make the STRING_TO_INTEGER() macro. Or is there a better way to handle the switch?
Portable example code:
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#define CHARS_TO_U32(c1, c2, c3, c4) (((uint32_t)(uint8_t)(c1) | \
(uint32_t)(uint8_t)(c2) << 8 | (uint32_t)(uint8_t)(c3) << 16 | \
(uint32_t)(uint8_t)(c4) << 24))
static inline uint32_t string_to_u32(const char *string)
{
assert(strlen(string) >= 4);
return CHARS_TO_U32(string[0], string[1], string[2], string[3]);
}
#define VERSION_2_3_7 CHARS_TO_U32('0', '2', '3', '7')
#define VERSION_2_4_1 CHARS_TO_U32('0', '2', '4', '1')
int main(int argc, char *argv[])
{
assert(argc == 2);
switch(string_to_u32(argv[1]))
{
case VERSION_2_3_7:
case VERSION_2_4_1:
puts("supported version");
return 0;
default:
puts("unsupported version");
return 1;
}
}
The code only assumes the existence of the integer types uint8_t and uint32_t and is agnostic to width and signedness of type char as well as endianness. It is free of collisions as long as the character encoding only uses values in range of uint8_t.
You switch on four-character-codes like this
switch (fourcc) {
case 'FROB':
}
Note the difference: "XXXX" is a string, 'XXXX' is a character/integer literal.
However, I would propose you use seperate version numbers instead, e.g.:
struct Version {
int major, minor, patch;
};
bool smaller (Version lhs, Version rhs) {
if (lhs.major < rhs.major) return true;
if (lhs.major > rhs.major) return false;
if (lhs.minor < rhs.minor) return true;
if (lhs.minor > rhs.minor) return false;
if (lhs.patch < rhs.patch) return true;
if (lhs.patch > rhs.patch) return false; // redundant, for readabiltiy
return false; // equal
}
Updated:
#define VERSION_2_3_7 '0237'
int versionstring_to_int(char * str)
{
char temp [ 1+ sizeof software_version.string ]; /* typically 1+4 */
if (!str || ! *str) return -1;
memcpy (temp, str, sizeof temp -1);
temp [sizeof temp -1] = 0;
return atoi (temp );
}
EDIT:
#define STRING_TO_INTEGER(s) versionstring_to_int(s)
#define VERSION_2_3_7 237
#define VERSION_2_4_1 241
switch ( STRING_TO_INTEGER( array[i].software_version.string) )
{
case VERSION_2_3_7:
break;
case VERSION_2_4_1:
break;
}