2012-02-09 3 views
1

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

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

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; 
} 

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

ответ

2

Это довольно просто, но очень удивительно, если вы привыкли к языку более высокого уровня. 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. Гоша, это было много работы, не так ли? Вот почему люди предпочитают писать веб-приложения на языке более высокого уровня. ;-)
+0

Я смирен; в 19 ​​дополнительных секунд вы перезаписали вещь. Ницца. :) – sarnold

+0

xmalloc не в C - и, честно говоря, у вас закончилась память - что вы собираетесь делать, чтобы восстановить в любом случае? Сбой - лучший вариант. –

+0

Итак, я проследил этот ответ самым легким, поскольку я вижу перераспределение памяти на контенте. Мне пришлось изменить xmalloc и xstrdup как strdup malloc и realloc, но, похоже, он работает нормально. Страницы, которые я буду тянуть, никогда не будут массивными страницами, они, вероятно, никогда не будут превышать 1k даже. Есть проблема с нехваткой памяти и т. Д. @AdrianCornish. –

1

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

char *content=malloc(256); 

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

2-я присваивание содержание работал нормально до этого - потому что вы устанавливаете указатель на точку к вашей константной строке. Если вы меняете контент в область памяти malloc'ed - тогда вам также понадобится strncpy ваша фиксированная строка в контент.

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

2

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

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

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

+0

Я понял на примере Zack, что у меня было 2 разных типа, хотя это то, о чем я совсем забыл, поскольку недавно работал над языками более высокого уровня. Функция сама по себе должна вернуть всю страницу, поскольку она обслуживается непосредственно из этой функции как перехват стандартной отображаемой страницы. Если бы у меня была функция, возвращающая содержимое со страницы, было бы лучше возвращаться по строкам для небольших объемов данных? Помимо распределения памяти, что было бы преимуществом для этого? –

+0

Мой комментарий действительно не относится к вашей конкретной ситуации, где желаемый результат - одна страница. Но, как правило, один гигантский текстовый буфер является субоптимальной структурой данных для работы. Почти каждая операция становится O (N). И вам нужно иметь дело с выбором заранее определенного предела памяти и устранением потенциальных переполнений или перераспределением по мере необходимости, включая копирование данных, что снова имеет время O (N). Часто лучше разбирать текст «на лету» в более управляемую структуру данных, будь то список, дерево, хэш или что-то еще, что требует ваше приложение. – AShelly

1

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

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

Используя весь этот код, убедитесь, что вы помните, сколько места вы выделили malloc(), и не записывайте больше данных, чем у вас есть, или вы получите больше сбоев.

1

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 должен найти конец каждый раз), но наименьшее изменение кода.

+0

Хорошая перезапись, но обратите внимание, что файл может расти после того, как вы измерили его размер, что повторяет вызов 'strcat()' небезопасной и потенциальной проблемы безопасности. – sarnold

+1

Это не переписывание - идея заключалась в том, чтобы добавить как можно меньше. Но, да, ваше право на растущий файл. По общему признанию, я бы этого не сделал, но я думал, что объяснить, как создать динамически растущий буфер, может быть слишком много, но, судя по принятому ответу (который был переписывается), я думаю, нет;) –

1

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

char content[256]; 

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

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

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

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

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

+0

Да, я был неправ. Я пропустил возврат локального var - я обновил свой ответ для хранения в куче. –

+0

Освобождение памяти для этой конкретной функции. Я на 100% лучше, поскольку у меня были некоторые серьезные проблемы в прошлом с утечками памяти, поэтому на данный момент я обычно пытаюсь освободить (xvar); просто проверить и убедиться, что это нужно быть свободным или нет, так как kdevelop кричит, если это не нужно быть свободным, поэтому я чувствую себя достаточно безопасно, чем сожалеть, и если он пропустит это приложение, произойдет сбой, а не вызывая утечку.Вопрос здесь, хотя будет, должен ли указатель быть NULLED впоследствии? –

+1

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

Смежные вопросы