Понимание строковых назначений в C - PullRequest
1 голос
/ 09 февраля 2012

Хорошо, я прочитал огромное количество ответов здесь, на SO, и во многих других местах, но я просто не могу понять эту простую функцию. Пожалуйста, прости меня за что-то такое простое, что я не делал код на c / c ++ более 8 лет, и я очень стараюсь переучиться, поэтому наберитесь терпения ...

Я пробовал много разных способов сделать это от назначения строки через параметр функции путем смещения значения до простого его возврата, но пока что ничего не работает. Я также не получаю ошибок во время компиляции, но я получаю segfaults во время выполнения. Мне бы очень хотелось выяснить, почему не работает следующая функция ... Я просто не понимаю, почему else возвращает отлично как type char * content, но strcat (content, line); не. Хотя страницы руководства для strcat показывают, что определение strcat должно быть (char * DEST, const char * SRC). Как я сейчас понимаю, попытка выполнить приведение к константному символу в строковой переменной в течение времени просто возвращает целое число указателю. Так что я в тупике и хотел бы получить образование у тех, у кого есть время!

char * getPage(char *filename) {
    FILE *pFile;
    char *content;
    pFile = fopen(filename, "r");
    if (pFile != NULL) {
        syslog(LOG_INFO,"Reading from:%s",filename);
        char line [256];
        while (fgets(line, sizeof line, pFile) != NULL) {
            syslog(LOG_INFO,">>>>>>>Fail Here<<<<<<<");
            strcat(content, line);
        }
        fclose(pFile);
    } else {
        content = "<!DOCTYPE html><html lang=\"en-US\"><head><title>Test</title></head><body><h1>Does Work</h1></body></html>";
        syslog(LOG_INFO,"Reading from:%s failed, serving static response",filename);
    }
    return content;
}

Очень ценю все отличные ответы в этом посте. Я бы поставил галочку у всех в обсуждении, но, к сожалению, не могу ...

Ответы [ 6 ]

2 голосов
/ 09 февраля 2012

Это довольно просто, но очень удивительно, если вы привыкли к языку более высокого уровня. C не управляет памятью для вас , а C на самом деле не имеет строк .Эта переменная content является указателем, а не строкой.Вы должны вручную выделить место, необходимое для строки, перед вызовом strcat.Правильный способ написания этого кода выглядит примерно так:

FILE *fp = fopen(filename, "r");
if (!fp) {
    syslog(LOG_INFO, "failed to open %s: %s", filename, strerror(errno));
    return xstrdup("<!DOCTYPE html><html lang=\"en-US\"><head><title>Test</title>"
                  "</head><body><h1>Does Work</h1></body></html>");
} else {
    size_t capacity = 4096, offset = 0, n;
    char *content = xmalloc(capacity);
    size_t n;
    while ((n = fread(content + offset, 1, capacity - offset, fp)) > 0) {
        offset += n;
        if (offset == capacity) {
            capacity *= 2;
            content = xrealloc(content, capacity);
        }
    }
    if (n < 0)
        syslog(LOG_INFO, "read error from %s: %s", filename, strerror(errno));
    content[offset] = '\0';
    fclose(fp);
    return content;
}

Примечания:

  1. Сообщения об ошибках, вызываемые сбоями ввода-вывода, должны ВСЕГДА включать strerror(errno).
  2. xmalloc, xrealloc и xstrdup являются функциями-обертками вокруг своих аналогов без начальных символов x;они сбивают программу, а не возвращают NULL.Это почти всегда менее печально, чем пытаться восстанавливаться из-за нехватки памяти вручную в каждом месте, где это может произойти.
  3. Я возвращаю xstrdup("...") вместо "..." в случае сбоя при открытиитак, чтобы звонящий всегда мог позвонить free(content).Вызов free для строкового литерала приведет к краху вашей программы.
  4. Черт возьми, это было много работы, не так ли?Вот почему люди предпочитают писать веб-приложения на языке более высокого уровня.; -)
2 голосов
/ 09 февраля 2012

Вам необходимо выделить память для content. Он должен быть достаточно большим для всего файла, как вы это делаете. Вы можете либо выделить огромный буфер заранее и надеяться на лучшее, либо выделить меньший и перераспределить его по мере необходимости.

Еще лучше было бы перестроить код, чтобы избежать необходимости хранить весь файл сразу, хотя, если вашему вызывающему абоненту нужна целая веб-страница в виде строки, это может быть сложно.

Обратите внимание, что вам необходимо возвращать одинаковый тип памяти из обоих путей кода. Вы не можете возвращать статическую строку иногда и выделенную кучу строку в другой раз. Это гарантированно вызывает головные боли и / или утечки памяти. Поэтому, если вы копируете содержимое файла в блок памяти, вам также следует скопировать статическую строку в блок того же типа.

1 голос
/ 09 февраля 2012

Более ранний ответ предложил решение:

char content[256];

Этот буфер не будет достаточно большим, чтобы содержать что-либо, кроме самых маленьких файлов и указатель content выходит из области видимости, когдаreturn content; выполнено.(Ваша предыдущая строка content = "static.."; в порядке, потому что строка помещается в .rodata сегмент данных , и ее указатель всегда будет указывать на одни и те же данные в течение всего времени жизни программы.)

Если вы выделите память для content с помощью malloc(3), вы можете «увеличить» пространство, требуемое с помощью realloc(3), но это может привести к ужасной ошибке - что бы вы ни указали, указатель долженочистка после выделения памяти, когда это делается с данными (или вы теряете память), и он не может просто вызвать free(3), поскольку указатель content может быть статически распределенной памятью.

Итак, у вас есть два простых выбора:

  • использовать strdup(3) до дублировать статическую строку каждый раз, когда вам это нужно, и использовать content = malloc(size); для не-статический путь
  • сделает ваш вызывающий ответственным за предоставление памяти;каждый вызов должен предоставлять достаточно памяти для обработки содержимого файла или статической строки.

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

1 голос
/ 09 февраля 2012

char *foo - это только указатель на некоторый фрагмент памяти, содержащий символы, которые образуют строку. Таким образом, вы не можете использовать strcat, потому что у вас нет памяти для копирования. Внутри оператора if вы выделяете локальную память в стеке с char line[256], который содержит строку, но, поскольку эта память является локальной для функции, она исчезнет после ее возврата, поэтому вы не сможете return line;.

Итак, что вы действительно хотите - это выделить некоторую постоянную память, например, с strdup или malloc, чтобы вы могли вернуть его из функции. Обратите внимание, что вы не можете смешивать константы и выделенную память (потому что пользователь вашей функции должен free память - что возможно, только если она не является константой).

Так что вы можете использовать что-то вроде этого:

char * getPage(const char *filename) {
    FILE *pFile;
    char *content;
    pFile = fopen(filename, "r");
    if (pFile != NULL) {
        syslog(LOG_INFO,"Reading from:%s",filename);
        /* check the size and allocate memory */
        fseek(pFile, 0, SEEK_END);
        if (!(content = malloc(ftell(pfile) + 1))) { /* out of memory ... */ }
        rewind(pFile);
        /* set the content to be empty */
        *content = 0;
        char line [256];
        while (fgets(line, sizeof line, pFile) != NULL) {
            syslog(LOG_INFO,">>>>>>>Fail Here<<<<<<<");
            strcat(content, line);
        }
        fclose(pFile);
    } else {
        content = strdup("<!DOCTYPE html><html lang=\"en-US\"><head><title>Test</title></head><body><h1>Does Work</h1></body></html>");
        syslog(LOG_INFO,"Reading from:%s failed, serving static response",filename);
    }
    return content;
}

Это не самый эффективный способ сделать это (потому что strcat должен каждый раз находить конец), но наименьшая модификация вашего кода.

1 голос
/ 09 февраля 2012

content - это дикий указатель; переменная содержит мусор, поэтому она указывает куда-то в левое поле. Когда вы копируете в него данные, используя strcat, данные попадают в случайное, вероятно, плохое место. Лекарство от этого состоит в том, чтобы сделать content точкой где-то хорошим Поскольку вы хотите, чтобы он пережил ваш вызов функции, он должен быть размещен где-то помимо стека вызовов функции. Вам нужно использовать malloc(), чтобы выделить место в куче. Тогда вызывающая сторона будет владеть памятью и должна вызвать free(), чтобы удалить ее, когда она больше не нужна.

Вам также потребуется изменить часть else, которая непосредственно присваивается content, чтобы использовать strcpy, чтобы free() всегда был действительным. Вы не можете освободить то, что не выделяли!

Во всем этом коде убедитесь, что вы помните, сколько места выделено с помощью malloc(), и не пишите больше данных, чем у вас есть, иначе вы получите больше аварий.

1 голос
/ 09 февраля 2012

content - это просто указатель на строку, а не на фактическую строку - в ней зарезервировано 0 байт для вашей строки.Вам нужно выделить достаточно памяти для хранения строки часа.Обратите внимание, что после того, как вам придется освободить его

char *content=malloc(256);

И ваш код должен быть в порядке - о, и я предлагаю использовать strncat

2-е назначение контента работало хорошо раньше - потому что выуказатель, указывающий на вашу константную строку.Если вы изменяете содержимое в область памяти, выделенную malloc, то вы также хотели бы поместить вашу фиксированную строку в содержимое.

В идеале, если вы можете использовать C ++ std :: string.

...