I’m having a problem returning a string from a function. I’m writing some code in C using Visual Studio 2022 that handles characters, but if the characters are control codes, I want to return the official names such as "NUL", "ESC" etc., rather than printing out the control characters themselves, which can produce spurious output, and in the case of a line feed mess up the printed output.
A much simplified version of the main function is as follows:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
typedef unsigned char BYTE;
void showASCIIContrChars(const char, char[]);
int main(void) {
int numEls = 500;
char conChars[4] = { '', '','', '' };
BYTE* bufA = calloc(numEls, 1);
for (int i = 0; i < 500; i++) {
bufA[i] = i;
showASCIIContrChars(bufA[i], conChars);
printf("%3d %sn", i, conChars);
}
}
The function called from main is as follows:
void showASCIIContrChars(const char c, char str[]) {
static bool first = true;
static char** contrs = NULL;
if (first) {
char *contrs[35] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
"BS ", "HT ", "LF ", "VT ", "FF ", "CR ", "SO ", "SI ",
"DEL", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
"CAN", "EM ", "SUB", "ESC", "FS ", "GS ", "RS ", "US ",
"SPA", "DEL", "INV"};
first = false;
}
if (c < 0 || c > 127) {
strcpy_s(str, 4, contrs[34]);
} else if (c < 33) {
strcpy_s(str, 4, contrs[c]); // <===
} else if (c == 127) {
strcpy_s(str, 4, contrs[33]);
} else {
str[0] = c;
str[1] = ' ';
str[2] = ' ';
}
}
The actual code does rather more, but this is a simplified part of it.
What I want showASCIIContrChars() to do is to return a string with 2 or 3 characters for the control codes 0 to 31, and 127, and for convenience return "SPA" for a space. "I’ve also included "INV" for invalid for codes outside the range 0 to 127. For codes between 33 and 126, which are the normal printable characters other than a space (code 32), I just want to return that character. In all cases any trailing spaces in the returned string are padded if needed, and terminated by a NUL, ” character.
The code compiles without errors, but on attempting to run it, an exception is thrown at the line indicated by "<==" with an error message: …. 0x000000C: Access violation reading location 0x followed by a long strings of 0s. Unfortunately copy and paste doesn’t work.
Incidentally I used typedef for an unsigned char as BYTE, but this should not have any effect. In many of my codes I use this so that all bytes have non-negative values, as in many cases the bytes do not represent characters, but the do here.
There must be a simple explanation for this error and fix it. I look forward to some help on this.
3
Answers
You are misunderstanding
static
and variable scope, and also over-thinking it. What you’ve done is define astatic char**
that is NULL, and never changes.When you enter your "first" section, you’re defining a new variable with the same name that only exists in that scope. Enabling all compiler warnings should inform you that the variable is unused.
As a result, even though you thought you were creating the table on first call, it was always NULL and so when you tried to access it you got a crash.
To fix this and simplify your function you just initialize the static table on definition. The compiler will only initialize it once, because it’s static. You can also avoid the need for testing negative values by making the first parameter unsigned.
Note I’ve also indicated the size of the
str
as a parameter. Although this is not necessarily enforced by the compiler, it at least indicates to the caller that they need a buffer this size. And compilers can issue warnings when you supply a buffer that is not large enough.To make the table a bit clearer, only the consecutive values are stored there, and string literals are used for the other cases (
DEL
andINV
).And to be a good citizen, we also NUL-terminate the string for all cases, meaning the caller does not need to initialize the buffer.
An alternative approach is to generate the entire table once, which means you don’t need any special logic when querying it:
Then your function becomes:
Or you can query the value without doing any copies:
If you really want your original function, it can now call the other so you don’t have multiple tables kicking around.
constrs[]
was being redeclared in an attempt to lazy initialize it. static variables are initialized at compile time so there was no point anyways.main()
seemed overly complicated. Also, ASCII is 7 bit (2^7) so not sure why you were driving it to 500.main()
should return an int.I would avoid the whole issue of copying strings into buffers.
This only returns pointers to immutable strings for control characters, and a NULL pointer for any non-control characters.
Then the caller can produce different output formatting for control characters and non-control characters.
Moving distinguishing between control characters and non-control characters to the caller even allows output formatting involving the actual non-control characters: