Перепрограммирование Calloc/Realloc на C с использованием указателей void

На самом деле я изучаю программирование на C, и моя школа фактически не позволяет нам использовать calloc/realloc без их перепрограммирования. Вот почему я прошу о помощи.

Вот моя проблема: я хочу использовать void *, чтобы мой код можно было использовать повторно, но я сталкиваюсь с проблемой «разыменования указателя void *», когда пытаюсь запустить свой массив. Я не могу подобрать тип конечного указателя.

Вот мои функции:

#include <stdlib.h>

void *my_calloc(size_t size, size_t n)              //n = number of bytes your type : sizeof(<whatever>)
{
    void *ptr = NULL;

    if (size < 1 || n < 1)
        return (NULL);
    ptr = malloc(n * (size + 1));
    if (ptr == NULL)
        return (NULL);
    for (int i = 0; i != (n * (size + 1)); i++) {
        *ptr = NULL;                                //Here is my problem
        ptr++;
    }
    return (ptr);
}

void *my_realloc(void *src, size_t size, size_t n)
{
    void *dst = NULL;
    int dst_len = 0;

    if (src == NULL || size < 0 || n < 1)
        return (NULL);
    dst_len = my_strlen(src) + size;
    if (dst_len == my_strlen(src))
        return (src);
    dst = my_calloc(dst_len, n);
    if (dst == NULL)
        return (NULL);
    for (int i = 0; src[i] != NULL;i++)
        dst[i] = src[i];                        //Here is the same problem...
    free(src);
    return (dst);
}

Я просто обнаружил проблему, когда писал свой пост, моя функция my_strlen может принимать только char *... поэтому мне нужна функция my_strlen, выглядящая так:

int my_strlen(void *str)
{
    int len = 0;

    while (str[len] != NULL) {                //same problem again...
        len++;
    }
    return (len);
}

Типичная функция, в которой я вызываю calloc / malloc, будет:

int main(void)
{
    char *foo = NULL;
    int size = 0;
    int size_to_add = 0;

    size = <any size>;
    //free(foo);                                //only if foo has been malloc before
    foo = my_calloc(size, typeof(*foo));

    //something

    size_to_add = <any size>;
    foo = my_realloc(foo, size_to_add, sizeof(*foo))

    //something

    free(foo);
    return (0);
}

Спасибо за попытку помочь мне.


person Dzious    schedule 11.01.2019    source источник
comment
почему бы вам просто не запомнить область, на которую указывает ptr?   -  person bruceg    schedule 11.01.2019
comment
Когда вы проверяете представление данных в хранилище, приведите указатель void к указателю на char или unsigned char. Вместо прямого использования ptr используйте, например. char *src = ptr; (и, возможно, char *dst; вместо void *new; для источника и назначения соответственно).   -  person Nominal Animal    schedule 11.01.2019
comment
ptr++;? Вы не можете сделать это с void *, так как нет типа и, следовательно, нет следующего адреса для увеличения.   -  person Andrew Henle    schedule 11.01.2019
comment
В my_realloc(), что вы пытаетесь сделать с new_len = my_strlen(ptr) + size;? Вы не можете найти размер, выделенный для старого блока с помощью функции типа strlen(), а realloc() не добавляет новый размер к старому размеру, а просто изменяет размер блока до нового размера (или выделяет новый, если это необходимо).   -  person Dmitri    schedule 11.01.2019
comment
Подумайте об этом... если вы выделите 1000 байт с помощью calloc(), а затем вызовете strlen() по указателю, какой размер он сообщит, учитывая, что вся память обнулена? Это блок размером 1000 байт, но сообщит ли об этом strlen()?   -  person Dmitri    schedule 11.01.2019
comment
@bruceg Я пока не могу установить память из-за моей школы...   -  person Dzious    schedule 11.01.2019
comment
@AndrewHenle Как я уже сказал, я новичок в C ... и я думал, что это может сработать, но нет, это не так.   -  person Dzious    schedule 11.01.2019
comment
@Dmitri На самом деле я просто хочу сохранить начало первого «\ 0», которое я нашел в src, в большем блоке. размер нового блока должен быть размером len строки, которую я нашел в src + размер, который я указываю в качестве параметра   -  person Dzious    schedule 11.01.2019
comment
@NominalAnimal спасибо за советы, я собираюсь изменить имена прямо сейчас, но я не могу использовать char * это не сработает для других типов переменных, таких как char **   -  person Dzious    schedule 11.01.2019
comment
@Dzious: Нет, я имел в виду, что вы используете src и dst типа char * внутри своих функций. size всегда в символах, потому что sizeof (char) == 1 в C. Ваши функции по-прежнему принимают и возвращают void *.   -  person Nominal Animal    schedule 11.01.2019
comment
@NominalAnimal хорошо, я понимаю, что вы имеете в виду, я проверил это, и это также работает, но у меня все еще есть та же проблема, что и с решением, данным dbush, когда я заполняю foo циклом for, в который я помещаю foo [i] = 'i', когда я пытаюсь напечатать благодаря printf, ничего не печатается (я знаю, что printf использует буфер, пока не найдет '\n', и я не вставлю '\n')   -  person Dzious    schedule 11.01.2019
comment
Зачем ненужное + 1 в ptr = malloc(n * (size + 1));?   -  person chux - Reinstate Monica    schedule 11.01.2019
comment
@chux +1 - это добавить '\ 0' в конец моей строки, это позволяет мне узнать, где заканчивается моя строка, без необходимости использовать строку string_len во всех моих функциях.   -  person Dzious    schedule 11.01.2019
comment
@Dzious Итак, вы хотите, чтобы ваш my_calloc() выделял больше памяти, чем calloc()?   -  person chux - Reinstate Monica    schedule 11.01.2019
comment
@chux на самом деле да, я никогда раньше не использовал calloc, и мне всегда приходилось добавлять +1 с malloc, поэтому я поместил его здесь, потому что знаю, что мне все равно придется добавить его   -  person Dzious    schedule 11.01.2019
comment
Когда хороший код добавляет 1 к своему запросу размера перед вызовом calloc(), когда этот код заменяется на my_calloc(), он просто будет менее эффективным. Хммм, я думаю ваша цель была бы ок, если бы вы назвали ее не my_calloc(), а, может быть, my_string_calloc(), чтобы уточнить, что ваша ***_calloc() отличается по функционалу от стандартной calloc().   -  person chux - Reinstate Monica    schedule 11.01.2019
comment
@chux хорошо, я сделал эту ошибку, потому что я еще никогда не использовал calloc(). Спасибо за помощь   -  person Dzious    schedule 11.01.2019
comment
@Dzious Не изменяйте детали / код вашего сообщения после получения ответов. Это создает движущуюся цель (потеря ясности) и делает недействительными ответы. Пост откатился. Как правило, можно добавлять новую информацию, но не применять код из ответов.   -  person chux - Reinstate Monica    schedule 11.01.2019
comment
хорошо, извините, я все отменю   -  person Dzious    schedule 11.01.2019


Ответы (2)


my_calloc() имеет разные проблемы:

Попытка вычисления указателя на void *

Это неопределенное поведение (UB).
Вместо этого сделайте ptr указателем на символ.

// void *ptr = NULL;
unsigned char *ptr = NULL;
...
ptr++;

Попытка разыменовать void *

Это тоже UB.
Вместо этого сделайте ptr указателем символа.

// void *ptr = NULL;
unsigned char *ptr = NULL;
...
// *ptr = NULL;
*ptr = '\0';

my_calloc() выделяет больше памяти, чем calloc()

Чтобы сделать то же самое, что и calloc(), не добавляйте его.

// ptr = malloc(n * (size + 1));
ptr = malloc(n * size);

Нет защиты от переполнения

my_calloc() не обнаруживает переполнения с n * (size + 1). Тест

// Note: it is known n > 0 at this point
if (SIZE_MAX/n > size+1) return NULL;
// or if OP drop the + 1  idea, 
if (SIZE_MAX/n > size) return NULL;

my_realloc() имеет разные проблемы:

Другая подпись

Я ожидаю, что цель «школа на самом деле не позволяет нам использовать calloc / realloc без их перепрограммирования» должна была создать замену realloc(), которой my_realloc() нет. Если требуется другая функция, рассмотрите новое имя

void *my_realloc(void *src, size_t size, size_t n)
// does not match
void *realloc(void *ptr, size_t size);

Не удалось обработать сокращающееся выделение

При копировании данных не учитывается, что новое выделение может быть меньше предыдущего. Это приводит к УБ.

Ненужный код

size < 0 всегда ложно

Утечка памяти

Приведенный ниже код не освобождает src перед возвратом. Кроме того, он ничего не выделяет, когда n>0. Это отличается от calloc(pit, 0) и calloc(NULL, 42).

// missing free, no allocation 
if (src == NULL || size < 0 || n < 1) {
    return (NULL);
}

Предполагаемая строка

my_strlen(src) предположим, что src указывает на допустимую строку. calloc() не предполагает этого.

person chux - Reinstate Monica    schedule 11.01.2019
comment
сначала спасибо за вашу помощь. но можете ли вы объяснить более точно тест на защиту от переполнения в my_calloc ? Еще одна вещь, которую я не понимаю, - это неспособность справиться с сокращением распределения, не могли бы вы также объяснить это? Наконец, я еще не знаю, как получить тип моего указателя, я все еще его ищу, поэтому my_realloc имеет не совсем тот же прототип, что и realloc() - person Dzious; 11.01.2019
comment
@Dzious Пример, предполагающий, что 32-битный size_t: my_realloc(ptr, 65536, 65536) будет выделять `100000 * 100000` --› после переполнения --› 1410065408 - меньше, чем ожидалось. С проверкой переполнения код вернет NULL в этой крайней попытке, что позволит вызывающему коду идентифицировать сбой. - person chux - Reinstate Monica; 11.01.2019
comment
@Dzious С for (int i = 0; src[i] != NULL;i++) dst[i] = src[i];, src[i] хорошо определен для всех i. Что если n==1, то dst[i] годится только для нескольких итераций. Отсюда и проблема сжатия. - person chux - Reinstate Monica; 11.01.2019
comment
хорошо, спасибо за пример, теперь я понимаю немного лучше - person Dzious; 11.01.2019
comment
@Dzious еще не знает, как получить тип моего указателя, который является сложным. Во-первых, my_realloc() нужно знать не тип, а размер предварительного распределения. Во-вторых, calloc() знает размер предыдущего распределения, но эта информация скрыта. Чтобы имитировать calloc(), все ваши выделения должны где-то скрывать размер - обычно это место перед указателем, предоставленным пользователю. Это может показаться немного сложным — так оно и есть, но это скрытая причина, по которой ваш класс подталкивает вас к повторной реализации кода. Удачи. P.S. Если вы пойдете по этому пути, вам также понадобится собственный my_free(). - person chux - Reinstate Monica; 11.01.2019
comment
Хорошо, большое спасибо, я пойду вперед с тем, что у меня есть на самом деле. я, конечно, придется сделать это в течение нескольких недель. В очередной раз благодарим за помощь - person Dzious; 11.01.2019
comment
извините, два последних вопроса, если я позволю (size + 1) в моем проекте (my_calloc), мне нужно поставить первую или вторую защиту от переполнения? Второй, как я могу найти SIZE_MAX? - person Dzious; 11.01.2019
comment
@Dzious Проверка должна проверить a*b > c - не вызывая переполнения. Используйте a > c/b или b > c/a на ваш выбор (убедитесь, что делитель не равен 0). SIZE_MAX находится в <stdint.h>. - person chux - Reinstate Monica; 11.01.2019

void — это неполный тип, поэтому вы не можете разыменовать void *. Однако вы можете привести его к char * или unsigned char * для доступа к отдельным байтам.

Итак, my_calloc может сделать это:

((char *)ptr)[i] = 0;

И my_realloc может сделать это:

((char *)dst)[i] = ((char *)src)[i]; 
person dbush    schedule 11.01.2019
comment
Во-первых, я хочу поблагодарить вас, это устранило мои ошибки. Теперь у меня новая проблема. В моем основном, когда я пытаюсь заполнить foo с помощью 'i', for (int i = 0; i != 3; i++) { foo[i] = 'i' }, а затем я печатаю i с помощью функции printf foo is не заполнен. не могли бы вы помочь, пожалуйста? (у foo достаточно блоков для заполнения) (извините, я не знаю, как добавить сюда настоящий код) - person Dzious; 11.01.2019