5

Do excuse me to the basic"ness" of this question. I am at a loss with pointers at times. I have a char * but I need to convert it to a char * const * to be able to correctly use it in the fts() function. How do i do that?

Thanks

Matt K
  • 13,370
  • 2
  • 32
  • 51
Lipika Deka
  • 3,774
  • 6
  • 43
  • 56
  • Are you clear on the fact that these things are not equivalent even if the `const` wasn't there? The first is a pointer to a character, the section is a const pointer to a character pointer. There's one more level of indirection in the second. The first you take one hop to your character, the second you take two. – Codie CodeMonkey Nov 10 '11 at 02:38
  • Related: http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.17 – Billy ONeal Nov 10 '11 at 02:40
  • @DeepYellow Yes, i am aware and hence shying for a direct casting. Will something like this do? Declare a char * const * variable say x . Let the char * variable be p. then x = &p. – Lipika Deka Nov 10 '11 at 02:42
  • My comment went seconds after similar answers below – Lipika Deka Nov 10 '11 at 02:44
  • You're asking for trouble if you do that. – Matt K Nov 10 '11 at 02:52

3 Answers3

12

You are not supposed to do that kind of conversion, because the types are not compatible.

About pointers and pointers to pointers

char * is a pointer to a string of characters, whereas char ** is an pointer to a pointer to a string of characters. (the const is a bonus). This probably means that instead of supplying a string of characters, you should provide an array of string of characters.

Those two things are clearly incompatible. Don't mix them with a cast.

About the fts_* API

To find the solution to your problem, we need to read the fts_* function API (e.g. at http://linux.die.net/man/3/fts), I see that:

FTS *fts_open(char * const *path_argv, int options,
          int (*compar)(const FTSENT **, const FTSENT **));

with your char * const * parameter path_argv, the description explains:

[...] If the compar() argument is NULL, the directory traversal order is in the order listed in path_argv for the root paths [...]

which confirms that the fts_open function is really expected a collection of paths, not one only path.

So I guess you need to pass to it something like the following:

char *p[] = { "/my/path", "/my/other/path", "/another/path", NULL } ;

About the const

Types in C and C++ are read from right to left. So if you have:

  • char * : pointer to char
  • char const * : pointer to const char (i.e. you can't modify the pointed string, but you can modify the pointer)
  • const char * : the same as char const *
  • char * const : const pointer to char (i.e. you can modify the pointed string, but you can't modify the pointer)
  • char ** : pointer to pointer to char
  • char * const * : pointer to const pointer to char (i.e. you can modify the pointer, and you can modify the strings of char, but you can't modify the intermediary pointer

It can be confusing, but reading them in the right-to-left order will be clear once you are more familiar with pointers (and if you programming in C or C++, you want to become familiar with pointers).

If we go back to the initial example (which sends a bunch of warnings on gcc with C99) :

char ** p = { "/my/path", "/my/other/path", "/another/path", NULL } ;

I played with the API, and you can feed it your paths two ways:

    char * p0 = "/my/path" ;
    char * p1 = "/my/other/path" ;
    char * p2 = "/another/path" ;

    /* with a fixed-size array */
    char * pp[] = {p0, p1, p2, NULL} ;

    FTS * fts_result = fts_open(pp, 0, NULL);

Edit 2011-11-10: snogglethorpe rightfully commented this solution is not a C89 valid solution, even if it compiles successfully with gcc (excluding pendantic + C89 flags). See Error: initializer element is not computable at load time for more info on that

or:

    /* with a malloc-ed array */
    char ** pp = malloc(4 * sizeof(char *)) ;
    pp[0] = p0 ;
    pp[1] = p1 ;
    pp[2] = p2 ;
    pp[3] = NULL ;

    FTS * fts_result2 = fts_open(pp, 0, NULL);
    free(pp) ;

Edit

After reading others answers, only two of them (mkb and moshbear) avoid the "just cast the data" error.

In my own answer, I forgot the NULL terminator for the array (but then I don't know the Linux API, nor the fts_* class of functions, so...)

Community
  • 1
  • 1
paercebal
  • 81,378
  • 38
  • 130
  • 159
  • It's kind of buried in the man page, but "The array must be terminated by a NULL pointer." so you should have an extra `NULL` pointer at the end, similar to how you call `execv()` and `execvp()` – Matt K Nov 10 '11 at 02:48
  • Note that with `char *p[]`, `p` will evaluate to a `char **`, and a `char **` can be implicitly converted to a `char * const *`. – caf Nov 10 '11 at 03:03
  • @Juggler : Yeah... It took some time, and I had some bugs corrected by mkb and caf... Those type declaration can be confusing, but don't get afraid of them. You'll get used to them (and easily un-used when, say, you write C# code for some months, as I did...) – paercebal Nov 10 '11 at 03:12
  • @caf and @mkb: Thanks for the corrections. I was so intent in explaining the `const` and everything I missed that `char * p[]` until I tested the code on gcc. – paercebal Nov 10 '11 at 03:14
  • wow you really went all out with this answer! BTW, I think using variables in an array initializer like that (`{p1, p2, p3, NULL}`) is only valid in C99, which might not be supported by all compilers, e.g., Microsoft has apparently declared they won't support C99... – snogglethorpe Nov 10 '11 at 08:07
  • @snogglethorpe : Thanks for the comment! About your doubt about using variables in an array initializer, I tested it on g++ (with no C++11 support), and it works. I'm not surprised: the size of the array, and the size of each of its items, are known at compile time. I asked a colleague of mine to test it at work, on VC++2008, and it works. – paercebal Nov 10 '11 at 08:46
  • @paercebal Not C++11, C99. Traditional C (C89) only supports static array initializers, but you're using an initializer containing variables (`p1`, `p2`, `p3`). By default, gcc supports this even in C89 mode as an extension, but if you compile like `gcc -std=c89 -pedantic-errors`, it will fail. I think this _is_ valid in C++, though, so if your friend tried it as a C++ program, it would have worked... – snogglethorpe Nov 10 '11 at 13:08
  • @snogglethorpe : You're right! I'll add a comment in the answer. – paercebal Nov 10 '11 at 18:22
2

You need to make a second array, which is NULL-terminated (because fts()'s first argument is argv).

E.g.

char *const buf2[2] = { buf, NULL };
fts(buf2);
moshbear
  • 3,282
  • 1
  • 19
  • 33
  • This does not do what you think it does; `buf2` is only a scalar, so only `buf` is used to initialise it and the `NULL` is ignored. – caf Nov 10 '11 at 03:05
  • Now you have another problem, although minor - `const char * const *` can't be implicitly converted to `char *const *` in C (although it can in C++). – caf Nov 10 '11 at 03:46
  • @caf Fixed, again. In C++, I think a `const_cast` is still needed. – moshbear Nov 10 '11 at 03:56
2

I'm assuming you're talking about fts_open:

FTS *fts_open(char * const *path_argv, int options,
                 int (*compar)(const FTSENT **, const FTSENT **));

What it's wanting of you is an array of const char* pointers, ie. an array of strings. The const is there just to tell you that it's not going to modify your strings, and gives you the opportunity to pass const strings.

Non-const variables can be treated as const, however you shouldn't usually treat them the other way around.

The first argument is just like argv passed to main, you could just have:

char *path_argv[] = { "/first_path/", "/second_path/", NULL };

It's important that the last element is NULL to indicate the end of the array.

Note also that path_argv could also be declared as:

char **path_argv

OR*

char * const *path_argv

Any of these are suitable types to be passed as the first argument to fts_open. You do, however, obviously have to initialize it differently than the above, but those are other ways you can declare path_argv. I made that unclear previously.

And then just fts_open(path_argv, options, compar), where options is your options, and compar is your compare function.

AusCBloke
  • 18,014
  • 6
  • 40
  • 44
  • @mkb oops my bad, thanks for catching that. copy, paste fail. – AusCBloke Nov 10 '11 at 02:54
  • `char *path_argv[]` *cannot* be rewritten as one of the other two forms if you use that initializer. A multiple-element initializer is only useful for initializing arrays or structs, not scalars (did anyone even try compiling their answers?) – caf Nov 10 '11 at 03:08
  • @caf sorry that's not what I meant, I meant `path_argv` could be declared as any of those and then passed to `fts_open`. Let me fix my bad wording. – AusCBloke Nov 10 '11 at 03:10