Создание структуры в куче?

Мне было приказано написать модель strdup, создав структуру String в куче, в которой хранится копия исходного кода. Я думаю, что успешно закодировал strdup, но я не уверен, что создал Struct в куче...

typedef 
struct String {
    int length;
    int capacity;
    unsigned check;
    char ptr[0];
} String;

char* modelstrdup(char* src){
    int capacity =0, length=0, i = 0 ;
    char *string;
    while ( src[length] != '\0'){
        length++;
    }
    capacity = length;
    string = malloc(sizeof(String) + capacity + 1);
    while ( i < length ){
        string[i] = src[i];
        i++;
    }
    string[i+1] = '\0';

    return string;
}   

person user133466    schedule 26.10.2009    source источник
comment
Дополнительные сведения о этот вопрос.   -  person Greg Hewgill    schedule 26.10.2009
comment
Используйте strlen() вместо написания собственного цикла для получения длины.   -  person Jonathan Leffler    schedule 26.10.2009


Ответы (4)


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

Код, который работает со стандартными C89 и C99

Ваш код, несколько исправлен...

typedef 
struct String {
    int length;
    int capacity;
    char *ptr;
} String;

char* modelstrdup(char* src){
    int length = strlen(src);
    char *space = malloc(sizeof(String) + length + 1);
    //String *string = space;  // Original code - compilers are not keen on it
    String *string = (String *)space;
    assert(space != 0);
    string->ptr = space + sizeof(String);  // or sizeof(*string)
    string->length = length;
    string->capacity = length + 1;
    strcpy(string->ptr, src);
    return string->ptr;
}

Этот код будет работать как в C89, так и в C99 (за исключением комментариев C99/C++). Вероятно, вы можете оптимизировать его для работы с «структурным взломом» (сохраняет указатель в структуре, но только если у вас есть компилятор C99). Утверждение является неоптимальной обработкой ошибок. Код не защищает себя от нулевого указателя для ввода. В этом контексте ни длина, ни емкость не дают никакой пользы — в наборе должны быть другие функции, которые смогут использовать эту информацию.

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


Код, который работает только со стандартным C99

В C99, раздел 6.7.2.1, параграф 16, описывает «гибкие элементы массива»:

В особом случае последний элемент структуры с более чем одним именованным членом может иметь тип неполного массива; это называется гибким членом массива. За двумя исключениями гибкий элемент массива игнорируется. Во-первых, размер структуры должен быть равен смещению последнего элемента идентичной структуры, которая заменяет гибкий элемент массива массивом неопределенной длины.106) Во-вторых, когда a . (или ->) имеет левый операнд, который является (указателем) структурой с гибким элементом массива, а правый операнд называет этот элемент, он ведет себя так, как если бы этот элемент был заменен самым длинным массивом (с тем же типом элемента ) это не сделало бы структуру больше, чем объект, к которому осуществляется доступ; смещение массива должно оставаться смещением гибкого элемента массива, даже если оно будет отличаться от смещения замещающего массива. Если этот массив не имеет элементов, он ведет себя так, как если бы он имел один элемент, но поведение не определено, если предпринимается какая-либо попытка получить доступ к этому элементу или сгенерировать указатель после него.

106 Длина не указана, чтобы учесть тот факт, что реализации могут задавать членам массива различные выравнивания в зависимости от их длины.

Используя «гибкий элемент массива», ваш код может стать:

typedef 
struct String {
    int length;
    int capacity;
    char ptr[];
} String;

char* modelstrdup(char* src){
    int length = strlen(src);
    String *string = malloc(sizeof(String) + length + 1);
    assert(string != 0);
    string->length = length;
    string->capacity = length + 1;
    strcpy(string->ptr, src);
    return string->ptr;
}

Этот код был принят GCC 4.0.1 как чистый, за исключением объявления функции (опции -Wall -Wextra). Предыдущий код требует приведения к 'String *string = (String *)space;' чтобы сообщить компилятору, что я имел в виду то, что сказал; Я исправил это и оставил комментарий, чтобы показать оригинал.


Использование «взлома структуры»

До C99 люди часто использовали «структурный хак», чтобы справиться с этим. Это очень похоже на код, показанный в вопросе, за исключением того, что размер массива равен 1, а не 0. Стандарт C не допускает нулевого размера массива.

typedef struct String {
    size_t length;
    size_t capacity;
    char ptr[1];
} String;

char* modelstrdup(char* src)
{
    size_t length = strlen(src);
    String *string = malloc(sizeof(String) + length + 1);
    assert(string != 0);
    string->length = length;
    string->capacity = length + 1;
    strcpy(string->ptr, src);
    return string->ptr;
}

Код, использующий нестандартное расширение GCC для C89 и C99.

Обозначение массива нулевого размера принимается GCC, если только вы не нажмете на него сильно - укажите стандарт ISO C и запросите педантическую точность. Таким образом, этот код компилируется нормально, если вы не используете gcc -Wall -Wextra -std=c99 -pedantic:

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

typedef
struct String {
    int length;
    int capacity;
    char ptr[0];
} String;

char* modelstrdup(char* src){
    int length = strlen(src);
    String *string = malloc(sizeof(String) + length + 1);
    assert(string != 0);
    string->length = length;
    string->capacity = length + 1;
    strcpy(string->ptr, src);
    return string->ptr;
}

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

person Jonathan Leffler    schedule 26.10.2009
comment
@metashockwave: a->b точно такое же, как (*a).b. Это просто стенография, которая лучше читается и не требует большого количества скобок. - person Greg Hewgill; 26.10.2009
comment
(*ptr).member ‹===› ptr->member. Я увидел это непонимание в вопросе с x-ref'd. Вам нужно привыкнуть к этому, если вы хотите писать идиоматические C или C++. - person Jonathan Leffler; 26.10.2009
comment
x-ref'd вопрос будет...? извините, я очень новичок в программировании. Я впервые знакомлюсь с программированием на C =) - person user133466; 26.10.2009
comment
Вопрос x-ref будет SO 1622416 (stackoverflow.com/questions/1622416), который вы задали ранее. - person Jonathan Leffler; 26.10.2009
comment
несовместимая ошибка в операторе String *string = space; типы - от 'char *' до 'String *' - person user133466; 26.10.2009
comment
Да, я отметил это в своем последнем дополнении (не исправляя). - person Jonathan Leffler; 26.10.2009
comment
@metashockwave, вы забыли инициализировать проверку. Кроме того, если вы внимательно следите за xref, емкость всегда должна быть фактической емкостью -sizeof(struct)-1 (в данном случае == длина), поскольку она возвращает емкость для символов, отличных от 0. Серьезно, ты должен решить это сам, если ты даже не знаешь -›, или ты забыл упомянуть, что не посещал ни одного занятия до сегодняшнего дня. - person ; 26.10.2009
comment
Вы заметили, что я изменил определение ptr в структуре? У вас был 'char ptr[0];' - что недопустимо в C; у вас не может быть массивов нулевого размера. У меня есть 'char *ptr;' что является совершенно хорошим значением lvalue, и именно поэтому я упомянул, что «взлом структуры» сохраняет вам указатель в структуре. Когда я беру первый блок кода с поправками и включаю <stdlib.h>, <string.h> и <assert.h>, он чисто компилируется под 'gcc -Wall -Wextra'. - person Jonathan Leffler; 26.10.2009
comment
да, я видел изменения, сделанные на указателе. И спасибо за подробное объяснение. Но в моем проекте явно указаны все члены структуры. Так что ptr[0] должен остаться... Я попытался скомпилировать то, что написано во втором блоке кода, и тогда я получил ошибку lvalue =( также нотация -› никогда не упоминалась в классе, наш профессор сказал нам использовать . но по какой-то причине, если я заменю -› на . , компилятор скажет мне переключиться обратно на -› - person user133466; 26.10.2009
comment
GCC допускает нотацию ptr[0]; Мне пришлось пнуть его с помощью '-std=c89 -pedantic' (или '-std=c99 -pedantic'), чтобы заставить его 'признаться, что ISO C запрещает массив нулевого размера 'ptr'. Это нестандартное расширение; вы не должны изучать это на данном этапе вашего образования. - person Jonathan Leffler; 26.10.2009
comment
@scape: Типа того. Я вижу это так: есть выделенный блок памяти размером с sizeof(String) плюс length + 1. Первые байты этого пространства будут рассматриваться как String; остаток будет содержать строку (массив char, заканчивающийся нулем). Начальное смещение остатка — это начальный адрес структуры плюс размер структуры. Выражение string->ptr = space + sizeof(String); присваивает указатель на область хранения строк указателю в String, поэтому, когда вы впоследствии пишете string->ptr[0], оно обращается к первому байту строки. - person Jonathan Leffler; 21.10.2017

Вы выделили немного памяти в куче, но не используете ее так, как если бы это была ваша структура. Переменная string в вашей функции имеет тип char *, а не тип struct String. Я думаю, что вы достаточно разумно дублируете функциональность strdup(), но я не понимаю причину такой структуры.

Примечание. Вам, вероятно, следует проверить свой вызов malloc() на наличие ошибки и вернуться соответствующим образом. Страница руководства для strdup() должна точно объяснить, что должна делать ваша функция.

person Carl Norum    schedule 26.10.2009

У вас есть. Malloc, new и т. д. используют кучу.

person popester    schedule 26.10.2009
comment
Если под new вы имели в виду оператор в C++, то нет, это не так. Он использует свободное хранилище, которое может быть таким же, как куча C, или может быть совершенно не связано. К сожалению, компьютеры глупы и требуют точности. - person Phil Miller; 26.10.2009

Да, malloc возвращает память в куче.

person bdonlan    schedule 26.10.2009