C: Присвоить токен strtok char * Segfault - PullRequest
0 голосов
/ 01 сентября 2018

Почему я получаю segfault с кодом ниже?

#include <stdio.h>

int main()
{
    char * tmp = "0.1";
    char * first = strtok(tmp, ".");
    return 0;
}

Отредактировано:

#include <stdio.h>

int main()
{
    char tmp[] = "0.1";
    char *first = strtok(tmp, ".");
    char *second = strtok(tmp, "."); // Yes, should be NULL
    printf("%s\n", first);       
    printf("Hello World\n");
    return 0;
}

Segfault можно воспроизвести на онлайн GDB здесь: https://www.onlinegdb.com/online_c_compiler

Ответы [ 4 ]

0 голосов
/ 02 сентября 2018

tmp не строковый литерал , как указывают несколько ответов или комментариев.

char * tmp = "0.1" это строковый литерал .

char tmp [] = "0.1" - это массив символов , и с ним могут выполняться все операции над массивами.

Segfault возникает из-за того, что объявление функции для strtok не найдено, так как string.h не включено, а компиляторы gcc или другие c-компоненты неявно объявляют тип возврата по умолчанию int .

Теперь, в зависимости от платформы, целочисленный размер может варьироваться, если размер int равен 4 байта , а размер указателя равен 8 байт соответственно

char * first = (int) strtok (tmp, ".");

Усечение происходит по адресу указателя, возвращенному strtok, а затем при печати вы отменяете ссылку адресным значением, содержащимся в начале, которое может быть областью памяти вне привязка, приводящая к ошибке сегментации или неопределенному поведению.

Если вы можете типизировать выходные данные strtok для типа, который равен 8 байтам (в моем случае это длинный), тогда не будет segfault, хотя это не совсем чистый способ.

Включите правильные заголовочные файлы , чтобы избежать неопределенного поведения.

0 голосов
/ 01 сентября 2018

С char * tmp = "0.1";, tmp указывает на строковый литерал, который нельзя изменить, и strtok пытается изменить строку, заменяя . на '\0'.

Другой подход, позволяющий избежать ошибки, заключается в использовании strchr для поиска точки и поля точности для печати ограниченного числа символов. Подстроки могут быть скопированы и в другие переменные.

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

int main ( void) {
    char * tmp = "0.1";
    char * first = strchr(tmp, '.');
    char * second = first + 1;
    if ( first) {
        printf ( "%.*s\n", first - tmp, tmp);
        printf ( "%s\n", second);
    }
    printf ( "Hello World\n");
    return 0;
}
0 голосов
/ 01 сентября 2018

Проблема с вашим первым кодом заключается в том, что tmp указывает на строковый литерал, который доступен только для чтения. Когда strtok пытается изменить строку, происходит сбой.


Проблема с вашим вторым кодом отсутствует:

#include <string.h>

Отсутствующий заголовок означает, что strtok не объявлено в вашей программе. Компилятор C предполагает, что все необъявленные функции возвращают int. Это не верно для strtok, который возвращает char *. Вероятная причина сбоя в вашем примере заключается в том, что код работает на 64-разрядной машине, где указатели имеют ширину 8 байт, а int - всего 4 байта. Это портит возвращаемое значение strtok, поэтому first является указателем мусора (и printf падает, когда пытается его использовать).

Вы можете подтвердить это сами, выполнив

char *first = strtok(tmp, ".");
printf("%p %p\n", (void *)tmp, (void *)first);

Адреса, напечатанные для tmp и first, должны быть идентичными (и если вы #include <string.h>).


Самое смешное, что gcc может предупредить вас об этих проблемах:

main.c: In function 'main':
main.c:6:19: warning: implicit declaration of function 'strtok' [-Wimplicit-function-declaration]
     char *first = strtok(tmp, ".");
                   ^
main.c:6:19: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
main.c:7:20: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
     char *second = strtok(tmp, "."); // Yes, should be NULL
                    ^

... и onlinegdb покажет вам эти предупреждения, но только в случае сбоя компиляции!

Чтобы увидеть предупреждения компилятора в onlinegdb, вам нужно добавить в код серьезную ошибку (например, поставив @ в последней строке файла).

0 голосов
/ 01 сентября 2018

Поведение функции strtok выглядит примерно так:

  1. Принять строку str или NULL и строку символов разделителей.
  2. Затем функция strtok начинает обрабатывать заданную строку str, в которой она читает символ за строкой, пока не встретит символ, присутствующий среди предоставленных символов-разделителей.
  3. Если число символов, с которыми он сталкивался до достижения строки разделителя, составляет> 0, то замените символ разделителя на «\ n» и вернет указатель на первый символ в этой итерации, который не был символом разделителя.
  4. В противном случае, если число символов, с которыми оно столкнулось до достижения строки разделителя, равно == 0, продолжайте итерацию оставшейся части строки без замены этого символа разделителя на '\ n'.

Я создал несколько фрагментов кода, которые помогут вам лучше понять природу функции, https://ideone.com/6NCcrR и https://ideone.com/KVI5n4 (<- извлечение выдержек из вашего кода в ваш код) </p>

Теперь, чтобы ответить на ваш вопрос, включая string.h заголовок и настройки char tmp[] = "0.1"; должен решить вашу проблему.

...