Suppose I have the following constants:
const char EASY = 'E';
const char NORMAL = 'N';
const char HARD = 'H';
const char LUNATIC = 'L';
I want LUNATIC to be greater than HARD, which is greater than NORMAL, which is greater than EASY.
How do I define them as such where the following will work:
int main(){
char diff1 = LUNATIC;
char diff2 = NORMAL;
if (diff1 > diff2){
printf("Lunatic is harder than normal");
}
return 0;
}
Use enumeration:
enum Difficulty
{
EASY,
NORMAL,
HARD,
LUNATIC
};
int main(){
char diff1 = LUNATIC;
char diff2 = NORMAL;
if (diff1 > diff2){
printf("Lunatic is harder than normal");
}
return 0;
}
Use enum { EASY, NORMAL, HARD, LUNATIC }; instead of your constant definitions and your code would start to work.
But, one thing that I am unable to understand is your need to compare the constants. You know the values as they are compile time constants!
There's several ways. The cleaner solution is to use enumerators, which are guaranteed to have increasing integral values:
typedef enum Difficulty {
Easy,
Normal,
Hard,
Lunatic
} Difficulty;
This can be used like this:
void foo(Difficulty a, Difficulty b)
{
if (a > b) {
// ...
} else
// ...
}
// ...
Difficulty a = Easy;
Difficulty b = Hard;
foo(a, b);
Other approaches are possible too. You can just use macros with increasing values:
#define EASY 0
#define NORMAL 1
#define HARD 2
#define LUNATIC 3
Your current approach works too, but there's no reason for you to use actual characters for the constants. You can just use numeric values instead:
const char EASY = 0;
const char NORMAL = 1;
const char HARD = 2;
const char LUNATIC = 3;
And of course there's no real reason as far as I can see that would require you to use char constants. You can just use int instead.
The first approach (using an enumerator) is usually preferred, as it gives you an actual typedef that you can use, which helps make code a bit more readable; if you see a variable of type Difficulty, then you immediately know what kind of values it's expected to hold.
One thing to keep in mind with enumerators is that they're compatible with integers. That means they don't really give you any type safety since you can assign any numerical value to them; it's mostly about writing cleaner code, not type safety.
Related
I have encountered a problem, and I haven't found an answer in the internet and forums. I hope you can help.
There is an interface requirement to update the LOG parameter. The interface passes in the parameter index and the value to be added, and the interface adds the value to the corresponding parameter. The interface requires simple implementation and no complicated judgments.
My idea is to create a mapping table that records the starting address and data type of each parameter. When the interface is called, the parameter address is obtained according to the parameter index and forced to be converted to the corresponding type, and then the addition operation is performed.
The problem with this solution is that the increase_log_info function is too complicated. How to simplify the increase_log_info function in C language? How parameter types can be mapped directly, rather than through an if...else condition.
Thanks in advance.
Note:
T_LOG_INFO is data structure definition and cannot be changed.
LOG_INFO_INCREASE is an update parameter interface provided
externally and cannot be changed.
Other codes can be changed.
#pragma once
/********************************can not be change, begin**********************************/
/* T_LOG_INFO is data structure definition and cannot be changed. */
typedef struct
{
unsigned short tx_num;
unsigned int tx_bytes;
unsigned short rx_num;
unsigned int rx_bytes;
unsigned char discard_num;
unsigned int discard_bytes;
// There are many parameters behind, not listed
}T_LOG_INFO;
T_LOG_INFO g_log_info;
/* This macro is called very frequently, and efficiency needs to be considered.
** LOG_INFO_INCREASE is an update parameter interface provided externally and cannot be changed. */
//#define LOG_INFO_INCREASE(para_idx, inc_val)
/********************************can not be change, end**********************************/
/********************************an alternative, begin**********************************/
enum
{
LOG_PARA_IDX_TX_NUM,
LOG_PARA_IDX_TX_BYTES,
LOG_PARA_IDX_RX_NUM,
LOG_PARA_IDX_RX_BYTES,
LOG_PARA_IDX_DISCARD_NUM,
LOG_PARA_IDX_DISCARD_BYTES,
LOG_PARA_IDX_MAX
};
enum
{
DATA_TYPE_U8,
DATA_TYPE_U16,
DATA_TYPE_U32
};
typedef struct
{
/* Indicates the offset of this parameter in the structure. */
unsigned char offset;
/* Indicates the data type of the parameter. */
unsigned char data_type;
}T_PARA_MAPPING;
/* This table can also be calculated during system initialization. */
T_PARA_MAPPING g_para_mapping_table[LOG_PARA_IDX_MAX] =
{
{0, DATA_TYPE_U16}, // LOG_PARA_IDX_TX_NUM
{4, DATA_TYPE_U32}, // LOG_PARA_IDX_TX_BYTES
{8, DATA_TYPE_U16}, // LOG_PARA_IDX_RX_NUM
{12, DATA_TYPE_U32}, // LOG_PARA_IDX_RX_BYTES
{16, DATA_TYPE_U8}, // LOG_PARA_IDX_DISCARD_NUM
{20, DATA_TYPE_U32} // LOG_PARA_IDX_DISCARD_BYTES
};
/* How to simplify the function??? especially to remove the judgment. */
static inline void increase_log_info(unsigned int para_idx, unsigned inc_val)
{
unsigned int data_type = g_para_mapping_table[para_idx].data_type;
/* Get the parameter address and cast it to the corresponding type pointer before adding. */
if (data_type == DATA_TYPE_U8)
{
*((unsigned char*)(((unsigned char*)&g_log_info) + g_para_mapping_table[para_idx].offset)) += inc_val;
}
else if (data_type == DATA_TYPE_U16)
{
*((unsigned short*)(((unsigned char*)&g_log_info) + g_para_mapping_table[para_idx].offset)) += inc_val;
}
else
{
*((unsigned int*)(((unsigned char*)&g_log_info) + g_para_mapping_table[para_idx].offset)) += inc_val;
}
}
/* This macro is called very frequently, and efficiency needs to be considered. */
#define LOG_INFO_INCREASE(para_idx, inc_val) increase_log_info(para_idx, inc_val)
/********************************an alternative, end**********************************/
/********************************test case, begin**********************************/
void increase_log_info_test()
{
LOG_INFO_INCREASE(LOG_PARA_IDX_TX_NUM, 1);
LOG_INFO_INCREASE(LOG_PARA_IDX_TX_NUM, 2);
LOG_INFO_INCREASE(LOG_PARA_IDX_TX_NUM, 3);
LOG_INFO_INCREASE(LOG_PARA_IDX_RX_BYTES, 10);
LOG_INFO_INCREASE(LOG_PARA_IDX_RX_BYTES, 20);
LOG_INFO_INCREASE(LOG_PARA_IDX_RX_BYTES, 30);
}
/********************************test case, end**********************************/
Quick answer with, maybe, syntax errors. But I hope the idea can be grasped.
I would prepare an array with a "datatype" for every member of the T_LOG_INFO struct:
{
unsigned short tx_num;
unsigned int tx_bytes;
unsigned short rx_num;
...
}
Copy/paste the above struct and, with a lot of editing, the array would be declared like this:
const char datatypes[LOG_PARA_IDX_MAX] = {
/* unsigned short tx_num; */ 2,
/* unsigned int tx_bytes; */ 4,
/* unsigned short rx_num; */ 2,
...
}
For lazyness, I used numbers like 2, 4 and so on. They indicate mainly the length, but they can carry other info (20=array of 20 char...).
Then I would declare another data structure (the final one):
struct datadesc {
int addr;
char kind;
} datadesc_array[LOG_PARA_IDX_MAX];
Prepare the table in code (the program itself) with:
address=&g_log_info;
for (int i=0; i<LOG_PARA_IDX_MAX; i++) {
datadesc_array[i].addr = address;
datadesc_array[i].kind = datatypes[i];
address += datatypes[i]; // simple like this if datatypes[] only accounts for length...
}
At this point, when you receive a command, you do:
param_addr = datadesc[para_idx].addr;
param_kind = datadesc[para_idx].kind;
switch (param_kind) {
case 2: // unsigned short
*(short*) param_addr = ...
break;
case 4: // unsigned int
*(unsigned int*) param_addr = ...
}
This way you have a reduced set of cases, just one for every data type you cope with. The only long work is done while preparing the datatypes[] array,
Basically, you can't. Data types in C are a purely static, compile-time construct. There's no such thing as a variable that holds a type or anything like that.
So you fundamentally can't avoid a chain of ifs, or else a switch, with the code for each different type written out separately. In principle you can avoid some of the repetition using macros, but that may actually end up being harder to read and understand.
The efficiency isn't so bad, though. A modern compiler is likely to handle an if chain in an efficient way, and switch might be even better.
Given this, your array of offsets and types may be unnecessary complexity. I would start with something much simpler, albeit longer:
void increase_log_info(unsigned int para_idx, unsigned inc_val) {
switch (para_idx) {
case LOG_PARA_IDX_TX_NUM:
g_log_info.tx_num += inc_val;
break;
case LOG_PARA_IDX_TX_BYTES:
g_log_info.tx_bytes += inc_val;
break;
// ...
}
}
It'll probably compile into a jump table. That's probably more efficient than what you have, as we don't have to keep accessing the mapping table somewhere else in memory and doing the corresponding address calculations. If it really can be proved to be too slow, you could consider some alternatives, but don't optimize prematurely!
This also has the advantage of being robust if the offset or types of any of the g_log_info members changes. In your code, you have to remember to manually update your mapping table, or else face very confusing bugs which the compiler will give you no help in detecting.
If you have an extremely large number of members, consider generating this function's C code with a script instead of by hand.
If this is inlined and called with a constant para_idx value, you can expect the compiler to propagate the constant and emit only the code to update the specific member in question.
I was wondering if it would be a good idea to use structs as pseudo namespaces (à la C++) to group constants which are functionally or conceptually related to each other.
static const struct {
const unsigned int START;
const unsigned int END;
} COUNTER = {.START = 1, .END = 100};
Is there any downside to this? If not, is it redundant (or maybe even unconvenient) to have both the struct instance and its members declared as const? Where should the constantness of these values be stated?
I was wondering if it would be a good idea to use structs as pseudo namespaces
Well, it CAN be a good idea. It's not intrinsically bad. An argument against is that if you feel that you need namespaces, then it's likely that C is the wrong language in the first place. But it can be used this way, and it is sometimes used this way.
Where should the constantness of these values be stated?
It's in general enough to declare the whole struct as const. But beware with pointers. This code is valid and will print "42":
int x = 5;
const struct {
int *p;
} S = {.p = &x };
int main()
{
*(S.p) = 42;
printf("%d\n", x);
}
In the above code, you are not allowed to change S.p so that it points to something else, but there is a difference between a const pointer and a pointer to const. So for pointer, it could be a good idea to add an extra const.
To clarify, the pointer p will be declared like it was a int * const p which means you cannot change the pointer itself, but in order to protect the data it's pointing to, you need const int *p. To get both, use const int * const p, but if the struct is declared as const, you'll get one of them "for free" so const int *p is enough to get both.
And if you consider pointers to pointers, well, think it through for a long time and test it to make sure it works the way you want.
From comments:
Why not enums?
Because this is not valid:
enum S {a = 5};
enum Y {a = 6};
The compiler will tell you that a is already defined. So enums is not good for emulating namespaces. Also, you cannot use enums for non-integers.
Is it a good practice to group related constants using structs in C?
It's opinion based. If it works for you, do it.
I wouldn't do like that i C. Instead I use #define
Like:
#define GROUPNAME_NAME
so in your case I would do
#define COUNTER_START 1
#define COUNTER_END 100
In C++ I would do:
const unsigned int COUNTER_START = 1;
const unsigned int COUNTER_END = 100;
The difference between C and C++ is due to differences in language specification.
I'm not quite into enums, but I need to create a datatype in C that represents numbers with just one digit.
I have tried with enums like:
enum digit {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Obiously the compiler interpret this as assigning the enumeration to a non-specified value.
Is there any way to "cheat" the compiler to let me do this?
Is there any other possible way to bound the range of an integer?
HAL2000's answer is good, still I will elaborate on it. As discussed in the comments, assert() is only the correct way if the setter is expected to be always called with a number between 0 and 9 and failure to do so is considered a programming error.
If you want validation instead (only set the value when it's in the correct range, otherwise report an error to the calling code), you'd change the setter like this:
static inline int
digit_set(struct digit *d, unsigned int val)
{
if (val > 9) return -1;
d->value = val;
return 0;
}
You can pick the returned values freely, -1 for error and 0 for success is very widespread in C, so I picked this. You could also prefer 1 for success and 0 for error, which would make your return value directly usable in a boolean expression.
This whole idea has a little weakness: Nothing will ever prevent you from directly setting the value member, bypassing your setter. Now, there's a solution to this as well, but it comes with costs. You can hide the definition of the struct in a separate translation unit, therefore calling code doesn't ever see its internals. It could look like this:
digit.h:
#ifndef DIGIT_H
#define DIGIT_H
typedef struct Digit Digit;
Digit *Digit_create(int val);
int Digit_setVal(Digit *self, int val);
int Digit_val(const Digit *self);
void Digit_destroy(Digit *self);
#endif
dgit.c:
#include <stdlib.h>
#include "digit.h"
struct Digit {
int val;
};
Digit *Digit_create(int val)
{
if (val < 0 || val > 9) return 0;
Digit *self = malloc(sizeof *self);
if (!self) return 0;
self->val = val;
return self;
}
int Digit_setVal(Digit *self, int val)
{
if (val < 0 || val > 9) return -1;
self->val = val;
return 0;
}
int Digit_val(const Digit *self)
{
return self->val;
}
void Digit_destroy(Digit *self)
{
free(self);
}
The cost:
This way of information hiding unfortunately requires a separate translation unit and dynamic allocation of the objects, which is expensive. I personally wouldn't recommend it for such a simple thing. But if you're e.g. writing a library and want to enforce it can't be used in a wrong way, it's worth a thought.
This basically is OOP realized in C, and for more complex structures, it really gives some benefit (also in structuring your program).
In the example above, the "constructor" Digit_create() makes sure to only ever construct valid objects. This is generally a good idea for robust code in more complex scenarios.
If you decide to implement something like this, make sure the deallocation function (here Digit_destroy()) works the same way as free(): It should silently ignore a null pointer. This is trivial here, because there's nothing else to do than calling free().
I recomend to use an abstract datatype with setters and getters if you really have to use range checking (otherwise use char/int and make sure not to write buggy code):
/* digit.h */
#include <assert.h>
struct digit
{
unsigned int value;
};
static inline unsigned int
digit_set(struct digit *d, unsigned int val)
{
assert(val <= 9);
d->val = val;
return val;
}
static inline unsigned int
digit_get(const struct digit *d)
{
return d->val;
}
You can't limit the range of values for either a type or variable in C unfortunately.
I would do a typedef char digit or typedef int digit and use that as the type for your variables, being sure not to assign anything besides 0 to 9 to your value. Note that this will not do any typechecking, and merely serves as documentation for you.
I have a struct:
struct A
{
unsigned int a, b, c, d, ...
}
I want to make a function:
unsigned int A_hash(const A* const var)
{
return ...
}
The number returned needs to be very very large as modulus for HashTable insertion will not work properly if A_hash(var) < myHashTable.capacity.
I've seen questions like this before like "Hash function that takes in two integers", "hash function that takes in five integers", etc but what aboutn integers? I'm looking for a more general algorithm for decent hashing. It doesn't need to be enterprise-level.
I was thinking perhaps start with a massive number like
return (0x7FFFFFFFF & a) + (0x7FFFFFFFF & b) + ...
but I don't think this will be good enough. I also don't know how to stop the A_hash function from overflowing but that may be another problem all together.
I think implicitly you are asking how it is possible to treat the entire object just like a long byte-stream, like #bruceg explained. If I'm wrong, then you might as well ignore this answer, because this is what I will address. Note that this solution does not apply merely for hashing, but for anything that requires you to treat data like bytes (such as copying from/writing to memory or files).
I think what you are looking for is merely reading byte by byte. For this you can insipre yourself from std::ostream::write (which is a C++ method though). For example, you could write A_hash in such a way that you could invoke it like this :
int hash = A_hash((char*)&a, sizeof(a)); // where 'a' is of type 'struct A'.
You could write A_hash, for example, like this:
unsigned int A_hash(char* data, unsigned int dataSize)
{
unsigned int hash = someValue;
for (unsigned int i = 0; i < dataSize; ++i)
{
char byte = data[i];
doSomethingWith(hash);
}
return hash;
}
The great advantage of this method is that you don't need to rewrite the function if you add/remove fields to your struct ; sizeof(A) will expand/reduce at compile-time. The other great advantage is that it works for any value, so you can reuse that function with any type you want, including int, another struct, an enum, a pointer, ...
For some reason this isn't working:
const char * str_reset_command = "\r\nReset";
const char * str_config_command = "\r\nConfig";
const char *commands[2] = {
&str_reset_command,
&str_config_command
};
Is this correct? (Is it code elsewhere that must be causing the problem?)
(I'm running it on a baseline 8bit microcontroller and have very limited debugging capabilities)
Clarification:
This is what I want to achieve:
const char * str_reset_command = "\r\nReset";
const char * str_config_command = "\r\nConfig";
const char * str_start_command = "\r\nStart";
const char * str_end_command = "\r\nEnd";
const char * const group0[1] = {
str_reset_command
}
const char * const group1[2] = {
str_reset_command,
str_start_command
}
const char * const group2[3] = {
str_reset_command,
str_start_command,
str_end_command
}
void doStuffToCharacter(unsigned char the_char){
// ....
}
void doStuffToGroup(char ** group, unsigned char num_strings){
for (unsigned char s=0; s < num_strings; s++){
unsigned char idx = 0;
while (group[s][idx]){
doStuffToCharacter(group[s][idx]);
}
}
}
void main (void){
doStuff(group0, 1);
doStuff(group1, 2);
doStuff(group2, 3);
}
As well as corrections, any neater suggestions of doing the above would be welcome.
Various combinations of the command strings need to be sent to a function for processing. All the strings will be in ROM, as will the groups of pointers to them.
You created an array of pointers, but you are passing the addresses of pointers (meaning, a pointer to a pointer). what you should do is this-
const char* commands[2]= {str_reset_command, str_config_command};
Doesn't directly answer your question, but why not try this:
typedef enum {
RESET_CMD,
CONFIG_CND
} cmd_id;
const char *commands[2] = {
"\r\nReset",
"\r\nConfig"
};
And than use it like this:
commands[RESET_CMD]
EDIT
I'll rewrite your clarification to match this method:
typedef enum {
RESET_CMD,
CONFIG_CND,
START_CMD,
END_CMD
} cmd_id;
const char *commands[4] = {
"\r\nReset",
"\r\nConfig",
"\r\nStart",
"\r\nEnd"
};
const cmd_id group0[1] = {
RESET_CMD
};
const cmd_id group1[2] = {
RESET_CMD,
START_CMD
};
const cmd_id group2[3] = {
RESET_CMD,
START_CMD,
END_CMD
};
void doStuffToCharacter(unsigned char the_char){
// ....
}
void doStuffToGroup(const cmd_id group[], unsigned char num_cmds){
for (unsigned char s=0; s < num_cmds; s++) {
unsigned char idx = 0;
while (commands[group[s]][idx]) {
doStuffToCharacter(commands[group[s]][idx++]);
}
}
}
void main (void){
doStuff(group0, 1);
doStuff(group1, 2);
doStuff(group2, 3);
}
You have an array of const char pointers, and you try to store in it pointers to const char pointers. Remove & operators and it should work.
If you have variables declared outside of function your initializers must be constant. Const doesn't count in this case. You will make an array of pointers to pointers and then use the address of the char* variables.
const char * str_reset_command = "\r\nReset";
const char * str_config_command = "\r\nConfig";
const char **commands[2] = {
&str_reset_command,
&str_config_command
};
Don't forget to dereference the pointer when you use the array.
const char* string = *commands[1] ; //actually it is *(commands[1]) but the [] operator has higher precedence anyway
Try this. It's not quite what you want, but it does the job.
#define RESET_COMMAND "Reset"
#define CONFIG_COMMAND "Config"
const char *commands[2] = {
RESET_COMMAND,
CONFIG_COMMAND,
};
int
main() {
...
}
You are using global variables. Are you really sure you need this approach?
By following your code as is, you defined str_reset_command as a const char *. This means that is a pointer to char, which also has the qualifier const. You have not to confuse "const" with const. :)
The compiler consider "const" to every expression build from expression involving literals.
On the other hand, the const qualifier means that you are defining a "variable" whose value cannot be modified, except in the very moment of its definition.
The expression "\r\nReset" is a "const" because is a string literal.
However const char * str_reset_command is not a "const", but a variable not able to be modified.
The difference is that the literals are "constants" for the compiler, because it can calculate their value in compiling time. However a const object is held as a variable, because its value may be determined in execution time. For example, consider the const parameters of several functions in <string.h>.
Your variable group0 is defined as a pointer to an array of char.
Since it is defined as a global variable, its initializer has to be a "const" (in compile time sense) value. But you have provided str_reset_command, which is not a literal (or an expression involving only literals). Thus, its value cannot be determined in compiling time and it cannot be used as initialize there.
Maybe you could try to write an initializer function:
const char * str_reset_command = "\r\nReset";
const char * str_config_command = "\r\nConfig";
const char * str_start_command = "\r\nStart";
const char * str_end_command = "\r\nEnd";
char * group0[1];
char * group1[2];
char * group2[3];
void initGroups(void) {
group2[0] = group1[0] = group0[0] = str_reset_command;
group2[1] = group1[1] = str_start_command;
group2[2] = str_end_command;
}
int main(void) {
initGroups();
// Do stuff...
}
I dropped the const qualifier since now we need to modify the pointers.
Anyway, the use of global variables has some side effects.
Try to modify this aspect, if possible.
EDIT
2nd TRY
I was thinking in your program, and I has changed completely your approach.
First of all, I don't understand why you are using all that "group[]" arrays.
Since you want to optimize memory resources, this is not optimal.
On the other hand, your "constant strings" seems to be only a few.
By assuming that they are not more than 8, the information of the strings involved in a given group can be hold in 1 byte.
I mean, you can define a "group" or a "set" by means of the bits of a byte, thus putting the bit 1 (on) if a given member belongs to the set, and 0 else.
Besides, since you accept the use of arrays of pointers to constant chars, I think that all your strings can be held in an array of constant char at the beggining of the code. This can be held in ROM, by means of a const declaration.
You have used names as str_reset, str_start, and so on.
It seems that you need this information to be clear for yourself.
This information can be preserved in the code by means of compiler-constants and/or enumerations, which have not any cost in the compiled program.
I have designed a code that use bit-masking.
This let you use up to 8 strings.
Since a bit-mask is a power of 2, this could not be used as an array index.
However, one can define in a very consequent way a list of enumaration constants with names going "in parallel" with the bit-mask constants.
IN particular, you will be capable or change the order of the enumeration, but your code will keep working.
To complete the picture, I have used a feature of C99/C11 standard (that you seems to use, because the way you are declaring the for statements), that allows us to initialize indidivual members of an array by writting the desired index.
Since the indexes now will have names given by an enumeration, you can trust in this technique, without pay attention to the actual indexes that are been used.
I have used <stdio.h> and printf() just to test the program. You can erase these lines.
#include <stdio.h>
#define bit0 0x01u /* Binary 0000 0001 */
#define bit1 0x02u /* Binary 0000 0010 */
#define bit2 0x04u /* Binary 0000 0100 */
#define bit3 0x08u /* Binary 0000 1000 */
#define bit4 0x10u /* Binary 0001 0000 */
#define bit5 0x20u /* Binary 0010 0000 */
#define bit6 0x40u /* Binary 0100 0000 */
#define bit7 0x80u /* Binary 1000 0000 */
enum {reset_command = 0, config_command, start_command, end_command};
#define bit_reset (1u << reset_command) /* Equal to bit0 */
#define bit_config (1u << config_command) /* Equal to bit1 */
#define bit_start (1u << start_command) /* Equal to bit2 */
#define bit_end (1u << end_command) /* Equal to bit3 */
const char * const str[] = {
[reset_command] = "\r\nReset",
[config_command] = "\r\nConfig",
[start_command] = "\r\nStart",
[end_command] = "\r\nEnd"
};
const unsigned char bitgroup0 = bit_reset;
const unsigned char bitgroup1 = bit_reset | bit_start;
const unsigned char bitgroup2 = bit_reset | bit_start | bit_end;
void doStuffToCharacter(unsigned char the_char){
printf("%c", the_char);
}
void doStuff(const char * const * str, unsigned char bitgroup){
printf("\n\nGroup: %hu\n", bitgroup);
for (unsigned char b=bitgroup, j=0; b; b >>= 1u, j++){
if (b & 1u) {
for(unsigned char idx = 0; str[j][idx]; idx++) {
doStuffToCharacter(str[j][idx]);
}
}
}
}
int main (void){
doStuff(str, bitgroup0);
doStuff(str, bitgroup1);
doStuff(str, bitgroup2);
}
As you can see, each "group" now needs only 1 byte. (Your approach used at least 1, 2 and 3 bytes).
The number of iterations in the for() statement does not exceed the greatest bit "on" in the groupbit parameter.
The requirement of having const char object held in const pointers is also fullfilled.
The size of the array is automatically determined by the compiler to hold the maximum index.
The for() loop iterates over a "byte" initialized to the "group" you passed as a parameter.
The last bit is tested against 1.
If this bit is 1, then some operation is done.
Else, this is skipped in it goes to the next iteration.
The last bit is dropped with the assignment b >>= 1u.
With each iteration, we need to walk away the index j of the array str.
Thus, the j-th bit is 1 if and only if the j-string is processed.
Next, every is repeated, until b has not more bits 1.
If you use "groups" whose bits 1 are all contiguous, then the program works in the same way that you expected in your example.
Now you are able to choose the operations you want, just switching the appropiated bits.
Your array declaration is inconsistent with the prototype of the function that uses it:
const char *commands[2] = { // <- declared as array of 2 pointers to char
doStuffToGroup(char ** group // <- used as pointer to pointer to char
It is a common C/C++ pitfall (though vectors used as a remplacement for vanilla arrays make that far less common in C++).
See this answer for a more detailed explanation.