3

It appears that there is a pointer compatibility problem using the function strsep to find the first word of a string. So far I always thought that char *s and char s[] are completely interchangeable. But it appears they are not. My program using an array on the stack fails with the message:

foo.c: In function ‘main’:
foo.c:9:21: warning: passing argument 1 of ‘strsep’ from incompatible pointer type [-Wincompatible-pointer-types]
  char *sub = strsep(&s2, " ");
                     ^
In file included from foo.c:2:0:
/usr/include/string.h:552:14: note: expected ‘char ** restrict’ but argument is of type ‘char (*)[200]’
 extern char *strsep (char **__restrict __stringp,

I do not understand the problem. The program using malloc works.

This works:

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

int main(void)
{
    char s1[] = "Hello world\0";
    char *s2 = malloc(strlen(s1)+1);
    strcpy(s2, s1);
    char *sub = strsep(&s2, " ");

    printf("%s\n", sub);

    return 0;
}

This doesn't:

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

int main(void)
{
    char s1[] = "Hello world\0";
    char s2[200];
    strcpy(s2, s1);
    char *sub = strsep(&s2, " ");

    printf("%s\n", sub);

    return 0;
}

What's the issue? (Sorry for strcpy). Why does it matter to functions wether a pointer points towards stack or heap? I understand why you can't access strings in the binary/text segment, but what's the issue with the stack?

trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    I'm pretty sure the `&s2` in your second example returns a `char **`? Turn on compiler warnings. – yyny Apr 05 '19 at 21:50
  • You should just use `s2` instead, or `&s2[0]` if you prefer. An array already gets converted to a pointer. – yyny Apr 05 '19 at 21:52
  • 2
    @YoYoYonnY those will result in a `char *`. `strsep` takes a `char **` for its first argument. – Christian Gibbons Apr 05 '19 at 21:55
  • You need a `char **` for `strsep`, so that it can modify it. In the second example, `&s2` has type `char (*)[200]`, which is quite different and incompatible. It needs to *modify* the pointer, which it clearly can't do in that case. – Tom Karzes Apr 05 '19 at 22:23
  • @YoYoYonnY Incorrect. `&s2` in the second example has type `char (*)[200]`, which is *never* compatible with `char **`. – Tom Karzes Apr 05 '19 at 22:42

2 Answers2

7
 note: expected ‘char ** restrict’ but argument is of type ‘char (*)[200]’

Your warning tells you exactly what the problem is. You have two different types.

char *s2;        /* declares a character pointer */

while

char s2[200];   /* declares an array of char[200] */

When you take the address of a pointer, the results is a pointer-to-pointer. When you take the address of an array, the results is a pointer-to-array. When you dereference a pointer-to-pointer, the result is a pointer. When you dereference a pointer-to-array, the result is an array.

strsep wasn't designed to take a pointer-to-array as an argument (which would prevent it from reallocating as needed)

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • But why are they completely interchangeable everywhere else? You can index through data using a pointer exactly like you do with an array with `pt[i]` – Theo Freeman Apr 05 '19 at 21:56
  • 1
    @TheoFreeman They are not completely interchangeable, but they are closely related. – Christian Gibbons Apr 05 '19 at 21:56
  • 2
    Understand that [C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p3) explains how an array is converted to a pointer on access. While that allows arrays with automatic storage duration and dynamically allocated arrays to be used interchangeably in some circumstances, it does not change the underlying type. The problem is when the function requires a *pointer-to-pointer*, e.g. `char *strsep(char **stringp, const char *delim);` or `ssize_t getline(char **lineptr, size_t *n, FILE *stream);` – David C. Rankin Apr 05 '19 at 21:58
2

@DavidRankin is correct as to why it doesn't work. But you can still write code so it can use a variable on the stack.

To use an array in place of malloc(), you can create another pointer to the array and use that as your parameter to strsep() as shown in version1() function.

I know this may just be an example, but your provided example with malloc() and strsep() can lead to memory errors since strsep() will update the pointer (it modifies the address it points to). So you must save off the original address returned by malloc() in order to properly free that memory. See version2() example.

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


void version1(void)
{
    char s1[] = "Hello world";
    char s2[200];
    char* s3 = s2;
    char *sub;

    strcpy(s3, s1); // or s2 in place of s3

    while ((sub = strsep(&s3, " ")))
    {
        printf("%s\n", sub);
    }
}

void version2(void)
{
    char s1[] = "Hello world";
    char *mymem = malloc(strlen(s1)+1);
    char *s2 = mymem;
    char *sub;

    strcpy(s2, s1);

    while ((sub = strsep(&s2, " ")))
    {
        printf("%s\n", sub);
    }

    free(mymem);
}

int main(int argc, char* argv[])
{
    printf("Version1\n");
    version1();

    printf("\nVersion2\n");
    version2();

    return 0;
}