This question is about how to solve my problem on the level of how I design my program. For a school project, I'm building a shell, which has several built-in functions. One of these function's purpose (cmd_type) is to check to see if the argument provided is in that list of functions. Here is a partial implementation of it:
int cmd_type(int argc, char *argv[]) {
if (argc == 2) {
for (int i = 0; i < BUILTIN_FUNC_COUNT; i++) {
if (strcmp(cmds_name[i], argv[1]) == 0) {
printf("%s is a shell builtin\n", argv[1]);
return 0; // found it
}
}
// still need to search path, call stat(path/cmd)
errmsg("not implemented! type", 1);
} else {
err_msg("type", 1);
}
}
Defining manual if statements for every function my shell supports sounds like a bad choice because the list might expand over time, and I need to store the list of function names anyway. So originally, I planned to define an array of function names and an array of their pointers, like so:
char cmds_name[BUILTIN_FUNC_COUNT-1][16];
char (*cmds_ptr)(int,*char[])[BUILTIN_FUNC_COUNT-1];
// make list of built-in funcs
strcpy(cmds_name[0], "exit");
strcpy(cmds_name[1], "cd");
// make list of func pointers
cmds_ptr[0] = &cmd_exit;
cmds_ptr[1] = &cmd_cd;
They're accessed like so:
// try builtin cmds
for (int i = 0; i < BUILTIN_FUNC_COUNT; i++) {
if (strcmp(cmds_name[i], argv[0]) == 0) {
last_cmd_err = (*cmds_ptr[i])(argc, argv);
continue; // we found it, so next loop
}
}
Then they'd each happily take (int argc, char *argv[]) as arguments. But the cmd_path() needs access to the list in addition to those arguments, so I'd have to define it as a global, or define a global pointer to it... In the process of researching this, I found this answer, saying a similar approach was really bad style: https://stackoverflow.com/a/41425477/5537652
So my questions are: Is this a good way to solve this problem, or should I just do if/else statements/is there a better way? Would you recommend a global pointer to the array of function names?
I am going to propose a structure of cmd_name and function pointer like this:
typedef struct{
char cmds_name[16];
char (*cmds_ptr)(int,*char[]);
} cmd_type;
Now define a static table of this type for all your cmds:
static const cmd_type cmd_table[] = {
{"exit", &cmd_exit},
{"cd", &cmd_cd},
.......
.......
};
Finally access it like this:
for (int i = 0; i < BUILTIN_FUNC_COUNT; i++) {
if (strcmp(cmd_table[i].cmds_name, argv[0]) == 0) {
last_cmd_err = (*cmd_table[i].cmds_ptr)(argc, argv);
continue; // we found it, so next loop
}
}
The decision to choose between if-else vs a global table is a matter of personal taste and coding style. I would prefer the above solution simply because it improves ** code readability** and reduces clutter. There may be other constraints in your environment that can influence your decision - like if the no of table entries is huge and there is a limitation on global memory space - the if-else route would be a better choice..
HTH!
I would not go with if-else statements. There is nothing wrong with solution (2) proposed in https://stackoverflow.com/a/41425477/5537652.
You could have a table with a string and a function to service an entry:
typedef struct cmd_desc
{
char cmd[80];
int builtin_cmd(int argc, char **argv, void *extra);
} CMD_DESC;
static CMD_DESC descTable[] =
{
{ "exit", cmd_exit },
{ "cd", cmd_cd },
{ "$ON_OPEN_CMD", OnOpenCmd },
{ "$OPEN_EXTRA_CMD", OpenExtraCmd },
{ "$AC", ActionCmd },
{ "$AD", ActionDataCmd },
{ "$EC", ExtraCmd },
{ "$TC", TextCmd },
{ "", NULL }
};
int cmd_exit (int argc, char **argv, void *extra)
{
//...
}
Access/execution:
for (int tokenIndex=0; strcmp(descTable[tokenIndex].cmd,""); tokenIndex++) //search table
{
if ( strcmp( (descTable[tokenIndex]).cmd, argv[0] ) == 0 )
{
int ret = (*(descTable[tokenIndex]).builtin_cmd( argc, argv, extra);
}
}
I used the above approach in a my applications and it worked well for me.
The table can be easily expanded and the readability of the table is better than if/else chain.
I've been having some difficulties with strings in C - especially when it comes to File I/O. I've looked through some previous threads to see how to make string arrays in C, and I have come up with this.
void CreateBiomes()
{
const int STRING_LENGTH = 32;
const int BIOME_COUNT = 63;
const char *biomes[BIOME_COUNT][STRING_LENGTH+1] = {"beaches", "birch_forest", "birch_forest_hills", "cold_beach", "deep_ocean", "desert", "desert_hills", "extreme_hills", "extreme_hills_with_trees", "forest", "forest_hills", "frozen_ocean", "frozen_river", "hell", "ice_flats", "ice_mountains", "jungle", "jungle_edge", "jungle_hills", "mesa", "mesa_clear_rock", "mesa_rock", "mushroom_island", "mushroom_island_shore", "mutated_birch_forest", "mutated_birch_forest_hills", "mutated_desert", "mutated_extreme_hills", "mutated_extreme_hills_with_trees", "mutated_forest", "mutated_ice_flats", "mutated_jungle", "mutated_jungle_edge", "mutated_mesa", "mutated_mesa_clear_rock", "mutated_mesa_rock", "mutated_plains", "mutated_redwood_taiga", "mutated_redwood_taiga_hills", "mutated_roofed_forest", "mutated_savanna", "mutated_savanna_rock", "mutated_swampland", "mutated_taiga", "mutated_taiga_cold", "ocean", "plains", "redwood_taiga", "redwood_taiga_hills", "river", "roofed_forest", "savanna", "savanna_rock", "sky", "smaller_extreme_hills", "stone_beach", "swampland", "taiga", "taiga_cold", "taiga_cold_hills", "taiga_hills", "void"};
for(int i = 0; i <= BIOME_COUNT; i++)
{
printf("%s\n", *biomes[i]);
}
return;
}
The issue is - this code only works for "beaches" and "mutated_mesa" before the program crashes. Everything compiles great, it just won't process any of the other strings I've listed in my array, instead, it prints a (null). Why is this?
Try replacing the declaration
const char *biomes[BIOME_COUNT][STRING_LENGTH+1]
as follows.
const char biomes[BIOME_COUNT][STRING_LENGTH+1]
Your array was a 2-D array of pointers, and, you indexed one too many in the printf loop.
I have changed this to a 1-D array of pointers, and also removed the hard coded sizes. Instead I replaced the last string "void" with a NULL pointer, and used that to control the loop.
#include <stdio.h>
void CreateBiomes(void)
{
const char *biomes[] = {"beaches", "birch_forest", "birch_forest_hills", "cold_beach", "deep_ocean", "desert", "desert_hills", "extreme_hills", "extreme_hills_with_trees", "forest", "forest_hills", "frozen_ocean", "frozen_river", "hell", "ice_flats", "ice_mountains", "jungle", "jungle_edge", "jungle_hills", "mesa", "mesa_clear_rock", "mesa_rock", "mushroom_island", "mushroom_island_shore", "mutated_birch_forest", "mutated_birch_forest_hills", "mutated_desert", "mutated_extreme_hills", "mutated_extreme_hills_with_trees", "mutated_forest", "mutated_ice_flats", "mutated_jungle", "mutated_jungle_edge", "mutated_mesa", "mutated_mesa_clear_rock", "mutated_mesa_rock", "mutated_plains", "mutated_redwood_taiga", "mutated_redwood_taiga_hills", "mutated_roofed_forest", "mutated_savanna", "mutated_savanna_rock", "mutated_swampland", "mutated_taiga", "mutated_taiga_cold", "ocean", "plains", "redwood_taiga", "redwood_taiga_hills", "river", "roofed_forest", "savanna", "savanna_rock", "sky", "smaller_extreme_hills", "stone_beach", "swampland", "taiga", "taiga_cold", "taiga_cold_hills", "taiga_hills",
NULL };
for(int i = 0; biomes[i] != NULL; i++) { // changed loop control
printf("%s\n", biomes[i]); // changed argument passed
}
}
int main(void){
CreateBiomes();
return 0;
}
Please note that this function won't do much good, because biomes, a local variable, will not be accessible after the function returns.
Try this way, your code should work.
void CreateBiomes()
{
int BIOME_COUNT = 63;
char *biomes[]= {"beaches", "birch_forest", "birch_forest_hills", "cold_beach", "deep_ocean", "desert", "desert_hills", "extreme_hills", "extreme_hills_with_trees", "forest", "forest_hills", "frozen_ocean", "frozen_river", "hell", "ice_flats", "ice_mountains", "jungle", "jungle_edge", "jungle_hills", "mesa", "mesa_clear_rock", "mesa_rock", "mushroom_island", "mushroom_island_shore", "mutated_birch_forest", "mutated_birch_forest_hills", "mutated_desert", "mutated_extreme_hills", "mutated_extreme_hills_with_trees", "mutated_forest", "mutated_ice_flats", "mutated_jungle", "mutated_jungle_edge", "mutated_mesa", "mutated_mesa_clear_rock", "mutated_mesa_rock", "mutated_plains", "mutated_redwood_taiga", "mutated_redwood_taiga_hills", "mutated_roofed_forest", "mutated_savanna", "mutated_savanna_rock", "mutated_swampland", "mutated_taiga", "mutated_taiga_cold", "ocean", "plains", "redwood_taiga", "redwood_taiga_hills", "river", "roofed_forest", "savanna", "savanna_rock", "sky", "smaller_extreme_hills", "stone_beach", "swampland", "taiga", "taiga_cold", "taiga_cold_hills", "taiga_hills", "void"};
int i;
for(i = 0; i < BIOME_COUNT-1; i++)
{
printf("%s\n", biomes[i]);
}
return;
}
I'm trying to print an array of structs that contain two strings. However my print function does not print more than two indices of the array. I am not sure why because it seems to me that the logic is correct.
This is the main function
const int MAX_LENGTH = 1024;
typedef struct song
{
char songName[MAX_LENGTH];
char artist[MAX_LENGTH];
} Song;
void getStringFromUserInput(char s[], int maxStrLength);
void printMusicLibrary(Song library[], int librarySize);
void printMusicLibraryTitle(void);
void printMusicLibrary (Song library[], int librarySize);
void printMusicLibraryEmpty(void);
int main(void) {
// Announce the start of the program
printf("%s", "Personal Music Library.\n\n");
printf("%s", "Commands are I (insert), S (sort by artist),\n"
"P (print), Q (quit).\n");
char response;
char input[MAX_LENGTH + 1];
int index = 0;
do {
printf("\nCommand?: ");
getStringFromUserInput(input, MAX_LENGTH);
// Response is the first character entered by user.
// Convert to uppercase to simplify later comparisons.
response = toupper(input[0]);
const int MAX_LIBRARY_SIZE = 100;
Song Library[MAX_LIBRARY_SIZE];
if (response == 'I') {
printf("Song name: ");
getStringFromUserInput(Library[index].songName, MAX_LENGTH);
printf("Artist: ");
getStringFromUserInput(Library[index].artist, MAX_LENGTH);
index++;
}
else if (response == 'P') {
// Print the music library.
int firstIndex = 0;
if (Library[firstIndex].songName[firstIndex] == '\0') {
printMusicLibraryEmpty();
} else {
printMusicLibraryTitle();
printMusicLibrary(Library, MAX_LIBRARY_SIZE);
}
This is my printing the library function
// This function will print the music library
void printMusicLibrary (Song library[], int librarySize) {
printf("\n");
bool empty = true;
for (int i = 0; (i < librarySize) && (!empty); i ++) {
empty = false;
if (library[i].songName[i] != '\0') {
printf("%s\n", library[i].songName);
printf("%s\n", library[i].artist);
printf("\n");
} else {
empty = true;
}
}
}
I think the problem is caused due to setting : empty = true outside the for loop and then checking (!empty) which will evaluate to false. What I am surprised by is how is it printing even two indices. You should set empty = false as you are already checking for the first index before the function call.
The logic has two ways to terminate the listing: 1) if the number of entries is reached, or 2) if any entry is empty.
I expect the second condition is stopping the listing before you expect. Probably the array wasn't built as expected (I didn't look at that part), or something is overwriting an early or middle entry.
you gave the definition as:
typedef struct song
{
char songName[MAX_LENGTH];
char artist[MAX_LENGTH];
}Song;
the later, you write if (library[i].songName[i] != '\0') which really seems strange: why would you index the songname string with the same index that the lib?
so I would naturally expect your print function to be:
// This function will print the music library
void printMusicLibrary (Song library[], int librarySize) {
for (int i = 0; i < librarySize; i ++) {
printf("%s\n%s\n\n", library[i].songName,
library[i].artist);
}
}
note that you may skip empty song names by testing library[i].songName[0] != '\0' (pay attention to the 0), but I think it would be better not to add them in the list (does an empty song name make sens?)
(If you decide to fix that, note that you have an other fishy place: if (Library[firstIndex].songName[firstIndex] == '\0') with the same pattern)
Is it possible to replace all of these "if, else if ..." with an array of function pointers in this example of code ?
if (strncmp(buff, "ls\n", 3) == 0)
my_ls();
else if (strncmp(buff, "cd\n", 3) == 0)
my_cd();
else if (strncmp(buff, "user\n", 5) == 0)
my_user();
else if (strncmp(buff, "pwd\n", 4) == 0)
my_pwd();
else if (strncmp(buff, "quit\n", 5) == 0)
my_quit();
I'm trying to get something like this :
void (*tab[5]) (void);
tab[0] = &my_ls;
tab[1] = &my_cd;
tab[2] = &my_user;
tab[3] = &my_pwd;
tab[4] = &my_quit;
I created a code to illustrate what you wanted to do, because I it's pretty entertaining.
#include <stdio.h>
#include <string.h>
// your functions
void my_ls() { puts("fun:my_ls") ;}
void my_cd() { puts("fun:my_cd") ;}
void my_user(){ puts("fun:my_user");}
void my_pwd() { puts("fun:my_pwd") ;}
void my_quit(){ puts("fun:my_quit");}
int main(int argc, char const *argv[])
{
char* buff="ls\n"; // the string you have to compare
void (*tab[5]) (void)={my_ls,my_cd,my_user,my_pwd,my_quit};
char *names[5]={"ls\n","cd\n","user\n","pwd\n","quit\n"};
int i;
for (i=0; i<5; i++)
{
if(strncmp(buff,names[i],strlen(names[i]) )==0){
tab[i]();
return 0;
}
}
return 0;
}
There are other ways to write it. Actually my_function is the same as &my_function since a function name alone is converted to the adress of the function.
Also tab[i]() is equivalent to (*tab[i])()... Those are weird behaviours but I think it's specified by C standard
There's no problem with an array of function pointers, but you'd need to convert the sequence of boolean strncmp() results to a single index.
If the list is long, the hash table idea might be a winner. For compact, simple code and easy maintenance, I've used an array of structs:
typedef struct cmdtable_t
{
void (*fptr)();
unsigned char length
char name[11];
} cmdtable_t, *pcmdtable_t;
cmd_table_t commands = {
{ my_ls, 2, "ls"},
{ my_cd, 2, "cd" },
{ my_user, 4, "user" },
...etc.
};
That could also be what a hash table entry looks like, could be sorted in advance to allow a binary search, or simply sequentially searched for a KISS version until you find out whether this needs optimizing at all.
I think you want a dictionary or hashtable:
Use buff as string key
Use function pointer as values
Suppose I'm trying to implement a "menu" of sorts that asks the user to enter a command and then calls the function that executes that command. Instead of having a block of conditionals for each command, I decided to declare an array of strings that contains each command name and then compares the user's input with the strings in that array to see what to do next.
Something like:
char* commands[] = {"cmd", "cmd1", "cmd2"};
Then:
while(strcmp(cmd, "end") != MATCH) {
printf("?:");
scanf("%s", cmd);
for(i = 0; i < CMD_NUMBER; i++) {
if(strcmp(cmd, commands[i]) == MATCH) {
/*do something */
}
}
}
Is there a way to call the function without having any conditionals or switch statements at this point? I was thinking of implementing a struct of function pointers, with a member for each command, and then using that, but I'm not exactly sure how or if that's even possible.
Create a struct that contains both the command and a pointer to the function:
typedef struct {
char * cmd;
void (* func)();
} Command_t;
Command_t commands[] = {
"cmd", func_cmd,
"cmd2", func_cmd2
};
Note: You need to declare the functions above this structure, else your compiler will balk at them.
(Edit) Just for completeness, you'd use this structure as
for(i = 0; i < sizeof(commands)/sizeof(commands[0]); i++)
{
if(!strcmp(cmd, commands[i].cmd))
{
commands[i].func();
break;
}
}