0

Suppose one has an array of strings:

const char  str1[] = "some/path";
const char  str2[] = "another/path";
const char  str3[] = "and/a/third/path";
const char* strs[3];
strs[0] = str1;
strs[1] = str2;
strs[2] = str3;

Retaining the original definitions of the strings (i.e., str1, str2, and str3), I would like to be able to modify the paths in the array by one character under control of a preprocessor directive. The simplest way I have found to do this requires dropping the const qualifier, addition of a superfluous null byte, and shifting each element to the right.

char  str1[] = "some/path\0";
char  str2[] = "another/path\0";
char  str3[] = "and/a/third/path\0";
char* strs[3];
strs[0] = str1;
strs[1] = str2;
strs[2] = str3;
#ifdef build_option
for(int i=0; i<3; i++) {
    for(int j=strlen(strs[i]); j>=0; j--) {
        strs[i][j] = strs[i][j-1];
    }   
    strs[i][0] = '.'; // prepend each path with this character
}   
#endif

fprintf(stdout, "%s\n%s\n%s\n", strs[0], strs[1], strs[2]);

Output:

.some/path
.another/path
.and/a/third/path

This works well enough, but I wonder if there is a simpler or more idiomatic way of doing the same thing, preferably statically (though it doesn't have to be).

Edits:

To clarify the scenario, only the original string is needed in the primary compilation route; in a lesser used, alternate compilation route, both the original and modified strings are required. In the latter route, the modified form is the primary one, serving the same role as the original throughout the program (however, access to the original is still needed).

Thanks to all for the many great suggestions; a combination of them were used to arrive at the following:

#include <stdio.h>

//#define build_option  // rare compilation route

#ifdef build_option
    #define STR_OFFSET  0
#else
    #define STR_OFFSET  1
#endif

int
main(int argc, char *argv[])
{
    const char  str1[] = ".some/path";
    const char  str2[] = ".another/path";
    const char  str3[] = ".and/a/third/path";
    const char* strs[3] = { str1,
                            str2,
                            str3 };

    for(int i=0; i<3; i++)
        fprintf(stdout, "%s\n",strs[i]+STR_OFFSET);

    #ifdef build_option
    fprintf(stdout, "\noriginal:\n");
    for(int i=0; i<3; i++)
        fprintf(stdout, "%s\n",strs[i]+1);
    #endif

    return 0;
}

Without build_option defined, this gives

some/path
another/path
and/a/third/path

With build_option defined, this gives

.some/path
.another/path
.and/a/third/path

original:
some/path
another/path
and/a/third/path
user001
  • 1,850
  • 4
  • 27
  • 42
  • I thought you wanted to retain the original contents of the strings. This code modifies them. – Barmar Apr 26 '19 at 00:03
  • @Barmar: And it also drops the `const` qualifier, which I would like to have kept. If you know a way to do it without modifying the contents, it would be preferable. – user001 Apr 26 '19 at 00:04
  • Use `malloc()` to allocate new strings, and copy to them with `strcpy()`. – Barmar Apr 26 '19 at 00:05
  • @Barmar: Thanks, was not sure if there was a way to do with static allocation. Sounds like it has to be done dynamically. – user001 Apr 26 '19 at 00:06

5 Answers5

2

Compile time solution:

#include <stdio.h>

#ifdef build_option
  #define PATH(str) "."str
#else
  #define PATH(str) str
#endif

const char  str1[] = PATH("some/path");
const char  str2[] = PATH("another/path");
const char  str3[] = PATH("and/a/third/path");
const char* strs[3] = { str1, str2, str3 };

int main()
{
  for(int i = 0; i < 3; ++i)
  {
    printf("%s\n", strs[i]);
  }
  return 0;
}

How does it work? Well, you just have to know that you can naturally concatenate string literals. Either google it or have a look at this stackoverflow thread. The rest is normal macro stuff. No runtime disadvantage, no memory allocation, no copying.

Michi
  • 681
  • 1
  • 7
  • 25
  • Thanks, that's another good idea. Used in combination with an offset (as suggested by Eric Postpischil) would provide access to both the original and modified strings in the compilation route with `build_option` defined. – user001 Apr 26 '19 at 01:24
1

Use malloc() to allocate new strings and copy from the literals to them.

const char* strs[] = {"some/path", "another/path", "and/a/third/path"};
const int numstrs = sizeof strs / sizeof strs[0];
char *newstrs[numstrs];
for (int i = 0; i < numstrs; i++) {
    newstrs[i] = malloc(strlen(strs[i] + 2)); // +2 for added prefix and null byte
    newstrs[i][0] = '.';
    strcpy(&newstrs[i][1], strs[i]);
}
Barmar
  • 741,623
  • 53
  • 500
  • 612
1

You can do it at compile time without modifying the content of the strings, but take note that this method only works if the characters you want to replace are at the end or the beginning of a string literal.

#ifdef build_option
  #define TK1 "."
#else
  #define TK1 ""
#endif

void main(void) {
  const char  str1[] = TK1"some/path";
  const char  str2[] = TK1"another/path";
  const char  str3[] = TK1"and/a/third/path";
  const char* strs[3];
  strs[0] = str1;
  strs[1] = str2;
  strs[2] = str3;
 }
Mark Benningfield
  • 2,800
  • 9
  • 31
  • 31
  • Thanks, making changes at compile time would be ideal. It doesn't seem possible though to adapt what you wrote to retain the original definition (I actually want to keep a copy of the original string as well). I tried `char str1[] = "some/path";` and then `strs[0] = TK1 str1;`, but it would not compile. Is there a way to modify your solution along these lines? – user001 Apr 26 '19 at 00:51
  • 1
    If you want the “original” string, just use `strs[i]+1` to point 1 character into the string (if `build_option` is defined, otherwise `strs[i]+0`). If you really want separate copies, there are ways to do that, using X macros. – Eric Postpischil Apr 26 '19 at 00:54
  • @user001 So, you're saying you want the strings to be defined *both* ways at compile time? – Mark Benningfield Apr 26 '19 at 00:55
  • Thanks, pointing a character ahead is a great idea. I could define IDX_START as 0 or 1 depending on the build option and then use strs[i]+IDX_START. This would still allow use of the original string. – user001 Apr 26 '19 at 00:59
  • In answer to your question about wanting strings compiled both ways: I will be building one way 99% of the time; in that case, I want only the original string defined. In the remaining cases, I want to having the original and the modified string since there needs to be some translation from original to modified. Your solution of having a variable start index seems the simplest possible solution. The original strings would be replaced by the modified strings in the code, but the start index would be defined as 1 for the normal compilation route and 0 for the alternate route. (...) – user001 Apr 26 '19 at 01:06
  • In the normal compilation route, the resulting string definitions would be essentially unchanged; in the alternate compilation route, access to both forms is preserved by using either 0 or 1 as an offset. – user001 Apr 26 '19 at 01:07
  • @user001: In future, you should probably put clarifications like that in your question. And, the suggestion to use a start index is Eric Postpischil's, not mine. – Mark Benningfield Apr 26 '19 at 01:09
1

There is no need for separate arrays unless you want to access them separately by name:

#if defined build_option
    #define MyPrefix "."
#else
    #define MyPrefix
#endif

const char *strs[] =
{
    MyPrefix "some/path",
    MyPrefix "another/path",
    MyPrefix "and/a/third/path",
};

Adjacent string literals are concatenated after preprocessing and before main compilation (semantic analysis and translation).

Here is a solution that defines both the original and modified strings:

#define DefineMyStrings(name, prefix) \
    char *name[] =                    \
    {                                 \
        prefix "some/path",         \
        prefix "another/path",      \
        prefix "and/a/third/path",  \
    };

DefineMyStrings(original,)
DefineMyStrings(prefixed, ".")


#include <stdio.h>


#define NumberOf(a) (sizeof (a) / sizeof *(a))

int main(void)
{
    for (int i = 0; i < NumberOf(original); ++i)
        printf("original[%d] = %s.\n", i, original[i]);

    for (int i = 0; i < NumberOf(prefixed); ++i)
        printf("prefixed[%d] = %s.\n", i, prefixed[i]);
}
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Thanks, that's a good point. Although it seems strange, I would like to retain a copy of the unmodified string. In that case, I could use `const char *strs[] = { ... }` with the original strings, and then have `#ifdef <...> #endif` with `<...>` replaced by the same assignment prepended by `MyPrefix` : `const char *strs2[] = { MyPrefix ... }`. – user001 Apr 26 '19 at 00:56
  • I hadn't noticed that you were the author of the comment in Mark Benningfield's answer. I think your idea of defining a compile-time offset offers the simplest solution to the problem. – user001 Apr 26 '19 at 01:14
  • Although there were many great answers, I chose to accept yours because of the helpful comment about variable offsets that you made in response to another answer, which I think offered the cleanest solution to the problem. Many thanks. – user001 Apr 26 '19 at 02:01
1

Here is a possible static solution

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static char *strsrc[] = {
        "some/path",
        "another/path",
        "and/a/third/path",
};
static char strdst[3][BUFSIZ];

int main(void)
{
        for (int i = 0; i < 3; i++) {
                strncpy(&strdst[i][1], strsrc[i], BUFSIZ); 
                strdst[i][0] = '.';
                puts(strdst[i]);
        }
        return 0;
}

I just copied the string to the destination offeseted and then overwrite the first character. Constants are already null terminated so no need to '\0' them.

Regards,

geckos
  • 5,687
  • 1
  • 41
  • 53