4

For background, I am working to create a version of minesweeper that runs in the terminal to become more familiar with coding bigger projects in c.

I am coding in VScode, to run on a Linux server (provided by the school) after compilation using gcc. I don't think it's a program version issue, but i can look those up if needed.

I am using/want to use ANSi color codes (from guide here: guide, and other here) to make the cells of my game different colors based on the number on them (eg 1 is yellow, 2 is blue, etc...). I have been able to do so by hard coding them (eg printf("%s [%d] %s", "\033[33m", cell.nearby_count, "\033[0m");), but want to use different ones based on variables (the number of nearby mines), without big switch statements or anything.

Min reproducible example:

#include "one.h"

test TEST_ONE = "ABCDEFG";
const test test_two = "HIJKLMN";
const test test_three = "OPQRSTU";
test test_four = "VWXYZ \n";

// construct array
// test array[4] = { TEST_ONE, test_two, test_three, test_four }; // errors when included (not commented), with "expression must have a constant value"
// in one.h

typedef const char* test;

extern test TEST_ONE;
const test test_two;
extern const test test_three;
test test_four;

// construct array
// test array[4] = { TEST_ONE, test_two, test_three, test_four }; // also has an error when included (not commented), with:

this error message.

Also for ideal purposes:

// two.c
#include "two.h"

// Include these moduals
#include <stdio.h>

// This the the main function, run at the start of the program
int main( void ) {
    for ( int i = 0; i < 4; i++ ) {
        printf( "%s\n", array[i] );
    }
    // Exit without error
    return 0;
}
// two.h
#include "one.h"

Right now I have these files: (Should I still include both examples? or is only the bare bones one needed?)

colors.h:

#ifndef color_guard
#define color_guard 1

typedef char* color;

extern const color BLANK;

// main colors
extern const color BLACK;

extern const color RED;
extern const color GREEN;
extern const color BLUE;

extern const color YELLOW;
extern const color MAGENTA;
extern const color CYAN;

extern const color WHITE;

// bright colors
...

extern const color BRIGHT_YELLOW;
extern const color BRIGHT_MAGENTA;
extern const color BRIGHT_CYAN;

extern const color BRIGHT_WHITE;

#endif

colors.c:

#include "colors.h"

const color BLANK = "\033[0m";

const color BLACK = "\033[30m";

const color RED = "\033[31m";
const color GREEN = "\033[32m";
const color YELLOW = "\033[33m";
const color BLUE = "\033[34m";

const color MAGENTA = "\003[35m";
const color CYAN = "\003[36m";

const color WHITE = "\033[37m";

...
const color BRIGHT_GREEN = "\033[92m";
const color BRIGHT_YELLOW = "\033[93m";
const color BRIGHT_BLUE = "\033[94m";
...

// I tried different things

color number_colors[9] = { WHITE, YELLOW, GREEN, BLUE, MAGENTA, CYAN, BRIGHT_YELLOW, BRIGHT_GREEN, BRIGHT_BLUE }; // this errors

char* number_colors[9] = { "\033[37m", YELLOW }; // this does on the second one, but not the first
number_colors[0] = WHITE; // these all error too
number_colors[1] = ( YELLOW );
number_colors[2] = { GREEN };

I get an 'Expression must have a constant value' error for the first options. and it says its type is (<error type>)<error-constant> in Intellisense. Error screenshot. It does work when I include the string literals themselves, but I want to be able to use the named values when defining the array for clarity.

Then in board.c, which are both included by main.h properly (I think?), I want to be able to look up a color based on a number. Like this_color = number_color[cell.nearby_count], and use it like printf("%s [%d] %s", this_color, cell.nearby_count, BLANK); to print my cells in differing colors.

I can use the colors by themselves in the other code, if I type them out by hand or just use the variable, but can't get the array of values to work how I want it too.

I have tried an enum instead, but that does the opposite of what I want, linking the string to the number and not the number to the string.
#define ing them seems to have the same problems?
I have tried changing around where the const and extern go, but no luck. and I cant find anything online that has the same problem.

I may just be overlooking something simple, I am a relative beginner. If so, I would be thankful for any advice you have to share. If it is impossible/not allowed by the compiler, why is that so, and do you all have any suggestions?

Thanks for the help!

Edited based on advice. Thanks!

New contributor
Librarian of Stars is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
2
  • 1
    The problem can be summarized easily as: never hide pointers behind a typedef. Stop doing that and watch all your problems disappear. Commented Nov 27 at 7:43
  • 1
    A note: MAGENTA and CYAN defined with wrong \003. Commented Nov 27 at 13:58

2 Answers 2

6

Variables declared at file scope must have constants as initializers, and the values of other file scope variables, even if declared as const, are not considered constants.

Macros perform direct token substitution, so you should use macros to define the named strings:

#define TEST_ONE "ABCDEFG"
#define test_two "HIJKLMN"
#define test_three "OPQRSTU"
#define test_four "VWXYZ \n"

Also, it's not a good idea to hide pointers behind a typedef, as that can make your code unclear as to whether or not you're actually using pointers.

Sign up to request clarification or add additional context in comments.

1 Comment

Re “the values of other file scope variables… are not considered constants”: This is no longer true. In C 2024, objects declared with constexpr may be used as constants. However, constexpr pointers may only be initialized with a null pointer.
3

I agree with the answer https://stackoverflow.com/a/79831372/4386427 from @dbush

Just want to add some extra information.

To be used as initializers, you'll need the type "const pointer to const char".
To get the "const pointer" part, try like:

typedef const char* test;

test const A = "AAA";
test const B = "BBB";
test array[2] = { A, B};

int main(void) {
    puts(array[0]);
    array[0] = "CCC";
    puts(array[0]);
    return 0;
}

or without the typedef do it like: const char* const A = "AAA";

See this What is the difference between const int*, const int * const, and int * const? for more information on pointers and const

4 Comments

This approach works with C++, not C. A and B are not compile time values, hence cannot be used to initialize a global array in test array[2] = { A, B };. clang supports it as an extension for C, but gcc does not, at least in older versions.
@chqrlie gcc decided to suddenly allow it upon a whim between versions, despite no changes being made to the C standard. So they suddenly broke 30 years of backwards compatibility with other gcc programs, for no good reason. The ivory tower guys at Bugzilla didn't think that was a big deal...
So they suddenly broke 30 years of backwards compatibility with other gcc programs while I agree this change is unwarranted, especially if -pedantic or -std=xxx are specified, it does not really break compatibility, in the sense that programs that compiled before still compile with the same semantics. It does break test suites that check for constraint violations.
@chqrlie My problem personally was exactly that: to live up to a certain coding standard, I must demonstrate compliance through a "compliance matrix" where you list which tool that handles diagnostics for a certain potential problem. Suddenly over night, gcc couldn't be trusted to do that any longer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.