sprintf() сошла с ума

Мне нужна помощь с этим, так как это сбивает меня с толку в моей программе C

У меня есть 2 строки (база и путь)

BASE: /home/steve/cps730
PATH: /page2.html

вот как printf читается непосредственно перед тем, как я вызываю sprintf, чтобы объединить их содержимое. вот блок кода

        int memory_alloc = strlen(filepath)+1;
        memory_alloc += strlen(BASE_DIR)+1;
        printf("\n\nAlloc: %d",memory_alloc);
        char *input = (char*)malloc(memory_alloc+9000);
        printf("\n\nBASE: %s\nPATH: %s\n\n",BASE_DIR,filepath);
        sprintf(input, "%s%s",BASE_DIR,filepath); //   :(

        printf("\n\nPATH: %s\n\n",input);

Теперь вы можете объяснить, как окончательный оператор printf возвращает

PATH: e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/stev

потому что вообще ничего не понимает.

** Я добавил 9000 в оператор malloc, чтобы предотвратить сбой программы (поскольку размер строки явно больше 31 байта.

Полный вывод

Alloc: 31

BASE: /home/steve/cps730
PATH: /page2.html



PATH: /home/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/steve/cps730e/stev

Sending: 
HTTP/1.0 404 Not Found
Date: Sat, 12 Sep 2009 19:01:53 GMT
Connection: close

РЕДАКТИРОВАТЬ........... Весь код, использующий эти переменные

const char *BASE_DIR = "/home/steve/cps730";
 char* handleHeader(char *header){
    //Method given by browser (will only take GET, POST, and HEAD)
    char *method;
    method = (char*)malloc(strlen(header)+1);
    strcpy(method,header);
    method = strtok(method," ");

    if(!strcmp(method,"GET")){
        char *path = strtok(NULL," ");
        if(!strcmp(path,"/")){
            path = (char*)malloc(strlen(BASE_DIR)+1+12);
            strcpy(path,"/index.html");
        }
        free(method);
        return readPage(path);
    }
    else if(!strcmp(method,"POST")){

    }
    else if(!strcmp(method,"HEAD")){

    }
    else{
        strcat(contents,"HTTP/1.1 501 Not Implemented\n");
                strcat(contents, "Date: Sat, 12 Sep 2009 19:01:53 GMT\n");
                strcat(contents, "Connection: close\n\n");
    }
    free(method);

}

//Return the contents of an HTML file
char* readPage(char* filepath){
    int memory_alloc = strlen(filepath)+1;
    memory_alloc += strlen(BASE_DIR)+1;
    printf("\n\nAlloc: %d",memory_alloc);
    char *input = (char*)malloc(memory_alloc+9000); 
    printf("\n\nBASE: %s\nPATH: %s\n\n",BASE_DIR,filepath);
    sprintf(input, "%s%s\0",BASE_DIR,filepath);

    printf("\n\nPATH: %s\n\n",input);

    FILE *file;
    file = fopen(input, "r");
    char temp[255];
    strcat(contents,"");

    if(file){
        strcat(contents, "HTTP/1.1 200 OK\n");
                strcat(contents, "Date: Sat, 12 Sep 2009 19:01:53 GMT\n");
                strcat(contents, "Content-Type: text/html; charset=utf-8\n");
                strcat(contents, "Connection: close\n\n");

        //Read the requested file line by line
        while(fgets(temp, 255, file)!=NULL) { 
            strcat(contents, temp);         
        }
    }
    else{
        strcat(contents, "HTTP/1.0 404 Not Found\n");
                strcat(contents, "Date: Sat, 12 Sep 2009 19:01:53 GMT\n");
                strcat(contents, "Connection: close\n\n");
    }

    return contents;
}

person Señor Reginold Francis    schedule 13.09.2009    source источник
comment
Можете ли вы показать полный вывод этого кода?   -  person Philippe Leybaert    schedule 13.09.2009
comment
@Aviator: Да - распечатка pre-malloc показывает, что ввод завершается нулем.   -  person Jonathan Leffler    schedule 13.09.2009


Ответы (8)


Вы вызываете readPage с недопустимым указателем path - он указывает на память, ранее выделенную указателем method, которая освобождается непосредственно перед вызовом readPage. Следующий malloc может повторно использовать эту память, и тогда может случиться что угодно...

person hjhill    schedule 13.09.2009
comment
Как вы рекомендуете мне это изменить? Я также должен добавить, что он работает нормально, когда выполняется файл index.html. - person Señor Reginold Francis; 13.09.2009
comment
Также есть утечка из выделенного пути ... @hjhill - молодец, предположив, что исходным кодом была функция readpage(). - person Jonathan Leffler; 13.09.2009
comment
Он работает нормально, когда выполняется материал index.html, потому что в этом случае вы выделяете другой кусок памяти для пути (который, кстати, никогда не освобождается). Хм... похоже, нужны большие изменения - я использую для этого другой ответ... - person hjhill; 13.09.2009

Ну, этого явно не может быть :-)

Я предполагаю, что ваша куча уже ужасно повреждена.

Я бы посмотрел на фактические значения указателя, используемые путем к файлу, вводом и базой. Интересно, обнаружите ли вы, что ввод очень близок к пути к файлу?

Я бы также посмотрел, как изначально были созданы путь к файлу, база и т. Д., Может ли у вас быть переполнение буфера?

person djna    schedule 13.09.2009
comment
Это тоже мое подозрение. Когда переменные изменяются, когда они не должны, это часто происходит из-за того, что что-то еще изменилось, что случайно затронуло старую переменную. Проверьте неинициализированные переменные, ошибочную арифметику указателя или переполнение буфера. - person GManNickG; 13.09.2009
comment
+1 за поврежденную кучу. Есть много инструментов для ранней диагностики (например, Valgrind). - person Filip Navara; 13.09.2009
comment
Ну, ясно, что это происходит - вопрос в том, почему это происходит. - person Jonathan Leffler; 13.09.2009
comment
@Jonathan Leffler То, что там был смайлик, шутка, возможно, не была смешной, но это была шутка. В остальной части моего ответа я пытался предположить, почему? - person djna; 13.09.2009

Попробуйте этот код:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    const char* BASE_DIR = "/home/steve/cps730";
    const char* filepath = "/page2.html";
    int memory_alloc = strlen(filepath);
    memory_alloc += strlen(BASE_DIR)+1;
    printf("\n\nAlloc: %d",memory_alloc);
    char *input = (char*)malloc(memory_alloc);
    printf("\n\nBASE: %s\nPATH: %s\n\n",BASE_DIR,filepath);
    sprintf(input, "%s%s",BASE_DIR,filepath); //   :(

    printf("\n\nPATH: %s\n\n",input);

    return 0;
}

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

(Кстати, я не добавлял +1 к обоим вызовам strlen, так как конкатенированная строка по-прежнему будет иметь только один нуль-терминатор.)

person UncleBens    schedule 13.09.2009

Поскольку значение BASE_DIR повторяется, BASE_DIR или filepath, вероятно, перекрываются с input памятью.

Убедитесь, что и BASE_DIR, и filepath действительно выделили память.

Первая попытка — просто сделать локальную копию BASE_DIR и filepath перед вызовом sprintf.

person Peter Olsson    schedule 13.09.2009

А-а-а - азарт погони, когда вопрос трансформируется, пока мы пытаемся решить проблему!

Текущий код выглядит так:

const char *BASE_DIR = "/home/steve/cps730";

//Handles the header sent by the browser
char* handleHeader(char *header){
    //Method given by browser (will only take GET, POST, and HEAD)
    char *method;
    method = (char*)malloc(strlen(header)+1);
    strcpy(method,header);
    method = strtok(method," ");

    if(!strcmp(method,"GET")){
        char *path = strtok(NULL," ");
        if(!strcmp(path,"/")){
                path = (char*)malloc(strlen(BASE_DIR)+1+12);
                strcpy(path,"/index.html");
        }
        free(method);
        return readPage(path);
    }
    ...

Вопрос: если это выполняется на веб-сервере, безопасно ли использовать небезопасную для потоков функцию strtok()? Я собираюсь предположить: «Да, это безопасно», хотя я не совсем в этом уверен. Вы напечатали строку header? Вы напечатали значение path? Вы действительно намеревались слить выделенные path? Вы поняли, что последовательность malloc() + strcpy() не копирует BASE_DIR в path?


Первоначальная версия кода заканчивалась:

 printf("\n\nPATH: %s\n\n", filepath);

Отсюда исходный предложенный частичный ответ:

Вы форматируете в input; вы печатаете из filepath?


Какова вероятность того, что filepath указывает на уже освобожденную память? Когда вы выделяете память, вы можете получить что-нибудь, происходящее с квазислучайной областью, на которую раньше указывал filepath. Другая возможность может заключаться в том, что filepath является указателем на локальную переменную в функции, которая вернула значение, поэтому он указывает на какое-то случайное место в стеке, которое повторно используется другим кодом, например sprintf().

Я также упомянул в комментарии, что вам, возможно, потребуется убедиться, что malloc() объявлено, и проверить возвращаемое из него значение. Приведение '(char *)' не является обязательным в C (оно есть в C++), и многие предпочитают не включать приведение, если код строго написан на C и не является двуязычным в C и C++.


Этот код работает для меня:

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

int main(void)
{
    const char *BASE_DIR = "/home/steve/cps730";
    const char *filepath = "/page2.html";

    int memory_alloc = strlen(filepath) + 1;
    memory_alloc += strlen(BASE_DIR) + 1;
    printf("\n\nAlloc: %d", memory_alloc);
    char *input = (char*)malloc(memory_alloc + 9000);
    printf("\n\nBASE: %s\nPATH: %s\n\n", BASE_DIR, filepath);
    sprintf(input, "%s%s", BASE_DIR, filepath);

    printf("\n\nPATH: %s\n\n", filepath);
    printf("\n\nPATH: %s\n\n", input);

    return(0);
}

Он производит посторонние пустые строки плюс:

Alloc: 31
BASE: /home/steve/cps730
PATH: /page2.html
PATH: /page2.html
PATH: /home/steve/cps730/page2.html
person Jonathan Leffler    schedule 13.09.2009
comment
Я тоже так думал, но проблема, похоже, в том, что filepath меняется от использования в sprintf. Он печатает filepath перед использованием sprintf, и я предполагаю, что он печатает правильно перед вызовом. После этого нет. - person GManNickG; 13.09.2009
comment
Я только что понял это, но это ничего не изменило - person Señor Reginold Francis; 13.09.2009
comment
Интересно, скрывал ли состав char * тот факт, что malloc() не был объявлен; что может привести к проблемам. Нет проверки возвращаемого значения malloc(); конечно, это вряд ли проблема, но... У меня нет хорошего объяснения того, почему filepath кажется измененным, но тогда у нас нет «рабочего» образца кода от спрашивающего. Фрагментарные фрагменты программы ведут к фрагментарным ответам... - person Jonathan Leffler; 13.09.2009
comment
Я не думаю, что вы связаны с Сэмом Леффлером? :-) - person DigitalRoss; 13.09.2009
comment
@digitalross: Сэм Леффлер и я носили одну и ту же фамилию — в записанной истории может быть общий предок, но я об этом не знаю; любые отношения крайне далеки. - person Jonathan Leffler; 13.09.2009

Самый простой способ выяснить, что происходит, — проследить выполнение в отладчике (возможно, перейдя к отслеживанию ассемблерного кода).

Несколько предположений о том, что может происходить:

  • повреждение памяти другим потоком (кажется маловероятным, если это легко повторить)
  • поврежденная куча (также кажется маловероятным, поскольку вы выгружаете 2 строки компонентов после вызова malloc())
  • как упоминал Джонатан Леффлер в комментарии, вам может не хватать заголовка (возможно, stdio.h), и компилятор генерирует неверную последовательность вызова/очистки стека для вызовов printf/sprintf. Я ожидаю, что вы увидите некоторые предупреждения во время компиляции, если это так, и вам следует принять к сведению.

Какой компилятор/цель вы используете?

person Michael Burr    schedule 13.09.2009

Чтобы сделать это правильно, я бы изменил код на:

/* CHANGED: allocate additional space for "index.html" */
method = (char*)malloc(strlen(header)+1+10);
strcpy(method,header);
method = strtok(method," ");

if(!strcmp(method,"GET")){
    char *path = strtok(NULL," ");
    if(!strcmp(path,"/")){
             /* CHANGED: don't allocate new memory, use previously allocated */
             strcpy(path,"/index.html");
    }
    /* CHANGED: call function first and free memory _after_ the call */
    char *result = readPage(path);
    free(method);
    return result;
}
person hjhill    schedule 13.09.2009

Предложения


В программе явно нет ничего плохого. (Обновление: ну, теперь кое-что очевидно. За первый час было опубликовано всего несколько строк, и в них не было серьезных ошибок. ) Вам придется опубликовать больше. Вот несколько идей:

  1. malloc(3) возвращает void *, поэтому его не нужно приводить. Если вы получаете предупреждение, это, скорее всего, означает, что вы не включили <stdlib.h>. Если вы не, вы должны. (Например, в 64-битной системе отсутствие прототипирования malloc(3) может быть довольно серьезным. Некоторые из 64-битных сред на самом деле не поддерживают K&R C. :-)
  2. Говоря о предупреждениях, пожалуйста, убедитесь, что вы включили их все. С помощью gcc вы можете включить большинство из них с помощью -Wall.
  3. Вы не проверяете возвращаемое значение malloc(3) на наличие ошибки.
  4. Используйте отладчик памяти, например Электрический забор. Вариантов много, смотрите мою ссылку.
person DigitalRoss    schedule 13.09.2009
comment
Он также не инициализирует память, которую получает от malloc. Разве он не должен обнулить его, если он планирует использовать его для строк, чтобы предотвратить любое искажение? - person mrduclaw; 13.09.2009
comment
Нет необходимости обнулять строку; sprintf() присваивает пробелу, а не объединяет. И если это была конкатенация, простой *input = '\0'; было бы достаточно. - person Jonathan Leffler; 13.09.2009
comment
Нет. В этом нет необходимости, учитывая его логику, и поэтому это либо не будет иметь никакого эффекта, либо скроет настоящую проблему. Обнуление памяти имеет большой смысл при распределении структур, но не так важно для хранения строк. - person DigitalRoss; 13.09.2009