Seg. fault в четырех строчках (С)

Модератор: Модераторы разделов

Аватара пользователя
fatboy
Сообщения: 156
ОС: Zenwalk Linux, Windows XP

Seg. fault в четырех строчках

Сообщение fatboy »

Объясните мне, пожалуйста, почему эта вещь выполняется нормально:

Код: Выделить всё

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

int main( void ){
    char *str = "/a/b/c/d";
    char *cp = strdup( str );
    char *p = dirname( cp );

    printf( "dirname = %s\n", p );

    return 0;
}

А эта вылетает по Segmentation fault:

Код: Выделить всё

int main( void ){
    char *str = "/a/b/c/d";
    char *p = dirname( str );

    printf( "dirname = %s\n", p );

    return 0;
}

?
Zenwalk 4.0
TOSHIBA Satellite A100
Спасибо сказали:
Аватара пользователя
amaora
Сообщения: 95
ОС: Slackware

Re: Seg. fault в четырех строчках

Сообщение amaora »

в man написано почему,

Код: Выделить всё

BUGS
       In  the glibc implementation of the POSIX versions of these functions they modify their argument, and segfault when called with a static string like "/usr/".  Before glibc 2.2.1, the glibc version of dirname() did not correctly handle pathnames with  trailing  '/'  characters, and generated a segfault if given a NULL argument.
кто здесь?
Спасибо сказали:
Аватара пользователя
fatboy
Сообщения: 156
ОС: Zenwalk Linux, Windows XP

Re: Seg. fault в четырех строчках

Сообщение fatboy »

Но я не со статической строкой ее вызываю, а с указателем на строку. У меня же не dirname( "/a/b/c/d" );
Zenwalk 4.0
TOSHIBA Satellite A100
Спасибо сказали:
Аватара пользователя
amaora
Сообщения: 95
ОС: Slackware

Re: Seg. fault в четырех строчках

Сообщение amaora »

адрес в функцию передаётся в обоих случаях один и тот же, указывающий на статичную строку.
кто здесь?
Спасибо сказали:
Аватара пользователя
Denjs
Сообщения: 1685
ОС: SuSe 10.2

Re: Seg. fault в четырех строчках

Сообщение Denjs »

почему-то я подозреваю что

Код: Выделить всё

int main( void ){
    char *str = "/a/b/c/d\0";
    char *p = dirname( str );
    printf( "dirname = %s\n", p );
    return 0;
}

должно отработать нормально.
---------------------------------------

Код: Выделить всё

char *strdup(const char *string);
char *dirname(char *path);


функции хотят видеть разные объекты на входе... первая как понимаю строку фиксированной длины (?), а другая - нуть-завершенную строку... "потому жёпа" когда dirname не встречает ожидаемый "нуль" и начинает продолжать читать "за пределами" "строки" ссылку на начало которой ему передали.
наверное.

гуру? поправляйте меня... а то я с удобствами QT типа QString совсем позабыл как "по простому" работать )))

====================================
PS2:ох не бейте меня сильно.... :
и насколько я понимаю, фраза

Код: Выделить всё

 char *str = "/a/b/c/d";

определяет фактически (const char*) который автоматически преобразуется к (char*) ? (у вас компилятор на эту строку что? даже warning не выдал? мне кажется должен был ругнуться....)

потому, полагаю, первый из приводимых примеров автора работает нормально.
strdup() берет (const char*), выделяет новый кусок памяти и создает там нуть-завершенную строку и отдает (char*) который нормально проглатывается dirname().

====================================
PS2: по топику: просто для того что бы автор покурил:
насколько я понимаю объявления

Код: Выделить всё

char *str = "/a/b/c/d";
и

Код: Выделить всё

char string1[8] = "/a/b/c/d";
char *str = string1[0];

совершенно эквивалентны с точки зрения того что они создают в памяти и того на что указывает str?
QDroid - Среда исполнения и фреймворк для QtScript.
OTPD - Открытые драйвера промышленных принтеров чеков и этикеток (кроссплатформенная подсистема печати).
Спасибо сказали:
MiK13
Сообщения: 1289
ОС: Linux Debian

Re: Seg. fault в четырех строчках

Сообщение MiK13 »

Denjs писал(а):
10.08.2008 06:12
насколько я понимаю объявления

Код: Выделить всё

char *str = "/a/b/c/d";
и

Код: Выделить всё

char string1[8] = "/a/b/c/d";
char *str = string1[0];

совершенно эквивалентны с точки зрения того что они создают в памяти и того на что указывает str?

Что-то я в этом очень сомневаюсь
Оператором char *str = string1[0] переменной str присавивается значение первого байта, на который указывает переменная string. И это будет (char *)0x002F
Правильнее было бы написать

Код: Выделить всё

char string1[8] = "/a/b/c/d";
char *str = string1;

или

Код: Выделить всё

char string1[8] = "/a/b/c/d";
char *str = &string1[0];

Но в любом случае строка завершаться нулевым байтом не будет (реально может и будет).
Спасибо сказали:
Аватара пользователя
fatboy
Сообщения: 156
ОС: Zenwalk Linux, Windows XP

Re: Seg. fault в четырех строчках

Сообщение fatboy »

Denjs писал(а):
10.08.2008 06:12
PS2:ох не бейте меня сильно.... :
и насколько я понимаю, фраза

Код: Выделить всё

 char *str = "/a/b/c/d";

определяет фактически (const char*) который автоматически преобразуется к (char*) ? (у вас компилятор на эту строку что? даже warning не выдал? мне кажется должен был ругнуться....)

потому, полагаю, первый из приводимых примеров автора работает нормально.
strdup() берет (const char*), выделяет новый кусок памяти и создает там нуть-завершенную строку и отдает (char*) который нормально проглатывается dirname().


Вот это меня натолкнуло на мысль, и очень внимательного перечтения man strdup до меня дошел смысл вот этой фразы:
The strdup() function returns a pointer to a new string which is a
duplicate of the string s. Memory for the new string is obtained with
malloc(3)
, and can be freed with free(3).


попробовав вот такой код:

Код: Выделить всё

const char *str = "/a/b/c/d";
char *p = dirname( strdup( str ) );
printf( "%s\n", p );

p = dirname( str );
printf( "%s\n", p );


как и ожидалось, я получил segfault на втором dirname(). Кстати, когда я делал

Код: Выделить всё

char *str = "/a/b/c/d";
dirname( str );


компилятор не ругнулся даже при включенном -Wall
а при

Код: Выделить всё

const char *str = "/a/b/c/d";
dirname( str );


выдал warning. Вот такие пироги. Так что, в принципе, проблема решена.
Zenwalk 4.0
TOSHIBA Satellite A100
Спасибо сказали:
Аватара пользователя
amaora
Сообщения: 95
ОС: Slackware

Re: Seg. fault в четырех строчках

Сообщение amaora »

Denjs

Код: Выделить всё

char *str = "/a/b/c/d\0";

Код: Выделить всё

char string1[8] = "/a/b/c/d";
char *str = string1[0];


:)

fatboy
посмотрите ещё вот это,

Код: Выделить всё

int main( void ){
    char str[] = "/a/b/c/d";
    printf( "dir[%i]=%s \n", sizeof(str), str );

    char *p = dirname( str );
    printf( "dir[%i]=%s dirname = %s\n", sizeof(str), str, p );

    return 0;
}


str здесь это массив символов, инициализированный статичной строкой. это похоже на strdup() только используется стековая память.
длину можно указать явно, это кол-во символов + 1 для завершающего 0-ля.
кто здесь?
Спасибо сказали:
Аватара пользователя
fatboy
Сообщения: 156
ОС: Zenwalk Linux, Windows XP

Re: Seg. fault в четырех строчках

Сообщение fatboy »

Дело было не в завершающем нуле или в чем-то подобном. Ваш код абсолютно нормально выполняется потому что Вы используете массив символов. Под него выделяется память malloc'ом и по-этому все ОК. В принципе, Вы были правы в Вашем первом посте, но я подумал что segfault должен быть только при dirname( "string" ) - вобще-то так в man'е и написано.
Zenwalk 4.0
TOSHIBA Satellite A100
Спасибо сказали:
MiK13
Сообщения: 1289
ОС: Linux Debian

Re: Seg. fault в четырех строчках

Сообщение MiK13 »

Точно сказать не могу, но предполагаю, что прав amaora: "/a/b/c/d" -- это не char *, а const char *.
А значит и располагается в памяти, которую можно только читать
В отличии от функции strdup, которая по malloc распределяет новый фрагмент памяти и копирует в неё строку, указанную аргументом, функция dirname модифицирует строку, на которую указывает аргумент.
Доказательством этого служит следующая программа:

Код: Выделить всё

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

int main( void ){
    char *str = "/a/b/c/d";
    char *cp = strdup( str );
    printf( "dirname1 = %s\n", cp);          // p.1
    char *p1 = dirname (cp);
    printf( "%p %p %p\n", str, cp, p1 );     // p.2
    printf( "dirname2 = %s\n", cp);          // p.3
    fflush(stdout);

    char *p = dirname( str );                // a.4

    printf( "dirname = %s\n", p );           // p.5

    return 0;
}

У меня она выдала следующий вывод:
dirname1 = /a/b/c/d
0x8048624 0x80497d0 0x80497d0
dirname2 = /a/b/c
Segmentation fault

Из этого вывода видно, что:
  • Оператор p.1 напечатал полное значение начальной строки
  • Оператор p.2 напечатал адреса строк str, cp и p1. Видно, что адрес у строк cp и p1 одинаковый
  • Как подтверждение этого, оператор p.3 напечатал усечённный вариант первоначальной строки. Т.е. можно было просто написать dirname(cp); и дальше использовать cp, а не p.
  • Оператор p.5 не выполнялся, т.е. Segmentation fault выскочило при выполнении оператора a.4.
Как вариант, можно было бы написать (правда, пока не проверил)

Код: Выделить всё

char *cp=dirname(strdup(str));


P.S. Решил посмотреть, что будет при использовании компилятора Open Watcom под Windows.
Результат получился другой. Правда, программу пришлось немного модифицировать -- компилятор не принимал объявления после исполняемых операторов
Программа получилась:

Код: Выделить всё

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

int main( void ) {
    char *str = "/a/b/c/d";
    char *cp = strdup( str );
    char *p1, *p;
    printf( "dirname1 = %s\n", cp);
    p1 = dirname(cp);
    printf( "%p %p %p\n", str, cp, p1 );
    printf( "dirname2 = %s\n", cp);
    fflush(stdout);

    p = dirname( str );

    printf( "dirname = %s\n", p );

    return 0;
}
И её вывод:
dirname1 = /a/b/c/d
00408004 00360788 00409000
dirname2 = /a/b/c/d
dirname = /a/b/c
Т.е. строки cp и p1 имели разные значения и программа нормально доработала до конца.
Видимо, в этой системе функция dirname возвращает указатель на свой внутренний массив типа char, в который и переносит строку-аргумент.
Доказательсвом этого служи предупреждение в help'е The dirname function is not re-entrant or thread-safe., а также програииа

Код: Выделить всё

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

int main( void ) {
    char *p1 = dirname("/a/b/c/d");
    char *p2 = dirname("/x/y/z");
    printf( "dirname1 = %s\n", p1);
    printf( "dirname2 = %s\n", p2);
    return 0;
}

И её вывод:
dirname1 = /x/y
dirname2 = /x/y
Т.е. после нахождения второго каталога первый теряется.


amaora писал(а):
10.08.2008 07:58
посмотрите ещё вот это,

Код: Выделить всё

int main( void ){
    char str[] = "/a/b/c/d";
    ...
}


str здесь это массив символов, инициализированный статичной строкой. это похоже на strdup() только используется стековая память.
длину можно указать явно, это кол-во символов + 1 для завершающего 0-ля.

По-моему, тут не стековая, а обычная память (хотя... смотря что понимать под стековой памятью).
Компилятор просто размещает строку "/a/b/c/d" в области констант (указывая, что память будет RO), а её адрес заносит в область данных (память RW) в то место, где он разместил переменную str
Спасибо сказали:
Аватара пользователя
fatboy
Сообщения: 156
ОС: Zenwalk Linux, Windows XP

Re: Seg. fault в четырех строчках

Сообщение fatboy »

Ну, да. Что-то вроде того. Получается, что в Watcom'овском варианте dirname() - это что-то типа

Код: Выделить всё

char *dirname( char *s ){
    char *tmp = strdup( s );
    // process tmp
    return tmp;
}


А в GCC'шном.

Код: Выделить всё

char *dirname( char *s ){
    // process s
    return s;
}
Zenwalk 4.0
TOSHIBA Satellite A100
Спасибо сказали:
Аватара пользователя
amaora
Сообщения: 95
ОС: Slackware

Re: Seg. fault в четырех строчках

Сообщение amaora »

fatboy писал(а):
10.08.2008 08:27
Дело было не в завершающем нуле или в чем-то подобном. Ваш код абсолютно нормально выполняется потому что Вы используете массив символов. Под него выделяется память malloc'ом и по-этому все ОК. В принципе, Вы были правы в Вашем первом посте, но я подумал что segfault должен быть только при dirname( "string" ) - вобще-то так в man'е и написано.


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

под массивы объявленные так,

Код: Выделить всё

char str[n];

память не выделяется malloc()'ом.

для кода внутри dirname() нет разницы между,

Код: Выделить всё

char *str = "/xx/xx";
dirname( str );

и

Код: Выделить всё

dirname( "/xx/xx" );


т.к. аргумент одинаково является адресом на область памяти в которую нельзя записать.
кто здесь?
Спасибо сказали:
MiK13
Сообщения: 1289
ОС: Linux Debian

Re: Seg. fault в четырех строчках

Сообщение MiK13 »

fatboy писал(а):
10.08.2008 09:02
Ну, да. Что-то вроде того. Получается, что в Watcom'овском варианте dirname() - это что-то типа

Код: Выделить всё

char *dirname( char *s ){
    char *tmp = strdup( s );
    // process tmp
    return tmp;
}


А в GCC'шном.

Код: Выделить всё

char *dirname( char *s ){
    // process s
    return s;
}

В смысле раличия -- да. Но Watcom'овский вариант реализован немного иначе. Дело в том, что в варианте char *tmp = strdup( s ); при каждом обращении под строке tmp функция strdup выделяла бы всё новую память. И возникла бы её утечка (правда, опять же -- с GCC'шной точки зрения).
Скорее всего, это реализовано:

Код: Выделить всё

char *dirname( char *s ){
    static char tmp[MAX_CHAR_IN_FILE_PATH+1];
    strcpy(tmp,s);
    // process tmp
    return tmp;
}

Константа MAX_CHAR_IN_FILE_PATH определяет максимальное число знаков в полном имени файла. На самом деле у неё другое имя (определено в каком-то из .h файлов), но я просто сейчас не помню какое.
Спасибо сказали:
Galaxy Master
Сообщения: 142
ОС: Debian GNU/Linux

Re: Seg. fault в четырех строчках

Сообщение Galaxy Master »

Напишите так:

char *aaa = "1234";
aaa[0] = 'Z';

и получите ту же ошибку....
ибо написано в мане про dirname

Both dirname() and basename() may modify the contents of path, so it
may be desirable to pass a copy when calling one of these functions.


значит и передавать нужно то, что можно изменить.
Спасибо сказали:
MiK13
Сообщения: 1289
ОС: Linux Debian

Re: Seg. fault в четырех строчках

Сообщение MiK13 »

Galaxy Master писал(а):
11.08.2008 16:00
Напишите так:

char *aaa = "1234";
aaa[0] = 'Z';

и получите ту же ошибку....

Написал простую программу:

Код: Выделить всё

#include <stdio.h>
char *s1 = "ABCDEFG";
char *s2;
main() {
  printf("Start program\n");
  s1[1]='_';
  s2 = "ABCDEFG";
  printf(" <%s> <%s>\n",s1,s2);
}

Страслировал компилятором OpenWatcom -- выдала
Start program
<A_CDEFG> <A_CDEFG>

т.е. нормально отработала. Причём, компилятор под обе (одинаковые) константы распределил одну и туже память. Которая измеилась.
Другими компиляторами проверить пока не могу.
Нашёл компилятор gcc. Программа, сделанная им, выдала только "Start program", после чего вылетела
В Linux'е программа вылетела по Segmentation fault

Galaxy Master писал(а):
11.08.2008 16:00
ибо написано в мане про dirname

Both dirname() and basename() may modify the contents of path, so it
may be desirable to pass a copy when calling one of these functions.


значит и передавать нужно то, что можно изменить.

Не совсем так. В мане написано, что эти функции могут изменять и поэтому желательно передавать копию.
Спасибо сказали:
Galaxy Master
Сообщения: 142
ОС: Debian GNU/Linux

Re: Seg. fault в четырех строчках

Сообщение Galaxy Master »

MiK13 писал(а):
14.08.2008 12:15
В мане написано, что эти функции могут изменять и поэтому желательно передавать копию.

В Linux'е программа вылетела по Segmentation fault


Ну раз вылетела, значит компилятор разместил ее там, где НЕЛЬЗЯ менять данные - это раз, а dirname - пытается это делать - это два.
И филосня могут / желательно и т.п. смысла не имеет, если не хотите получить нестабильную программу.
Спасибо сказали:
MiK13
Сообщения: 1289
ОС: Linux Debian

Re: Seg. fault в четырех строчках

Сообщение MiK13 »

Galaxy Master писал(а):
14.08.2008 16:59
И филосня могут / желательно и т.п. смысла не имеет, если не хотите получить нестабильную программу.

Всё правильно. Только есть одна проблема. Чтобы написать стабильную программу, надо хорошо изучить как работают все объекты, из которых она состоит. А также собенности работы функций в конкретной системе программирования. Но очень часто этого не делается.
С тем же самим dirname. В одной системе это может работать, причём нормально, а в другой -- нет.
Более того, как я понял, gcc модифицирует сам передаваемый параметр, а Watcom создаёт копию в своей статической области. В результате может оказаться, что программа, нормально работающая в линуксе, в другой системе начнёт выдавать некорректный результат
Спасибо сказали:
Galaxy Master
Сообщения: 142
ОС: Debian GNU/Linux

Re: Seg. fault в четырех строчках

Сообщение Galaxy Master »

MiK13 писал(а):
14.08.2008 19:08
В результате может оказаться, что программа, нормально работающая в линуксе, в другой системе начнёт выдавать некорректный результат


Используйте кроссплатформенные библиотеки а-ля QT и избегайте системного API.
Или внимательно читайте ман каждой функи и обходите малейшие спорные моменты.
Спасибо сказали: