Владислав очень хорошо проиллюстрировал разницу; Но что это значит? Различная организация памяти имеет пару последствий:
struct container
во втором примере можно использовать как есть; это не требует никакой инициализации. Например,
typedef struct {
uint8_t buffer[4];
} Container;
Container c;
strcpy(c.buffer, "Yes");
в порядке, но, скорее всего, с первой версией будет взломано sh, поскольку указатель c.buffer
будет неинициализирован и будет содержать недопустимый адрес.
Производительность структуры со встроенным буфером, вероятно, лучше, потому что на
init()
сохраняется одно выделение. Локальная память также может быть проблемой: при динамическом распределении c буферная память, возможно, находится далеко от структуры памяти, поэтому она не находится в кэше.
Еще один момент. Здесь вы имитируете C ++, а init()
играет роль фабрики с конструктором.
К сожалению, пока доступно определение struct Container
, любой пользователь может создать неинициализированный Container
и использовать его, что приведет к катастрофическим последствиям. (В C ++ мы объявили бы конструктор частным, но мы не можем сделать это в C.)
Единственный способ предотвратить создание пользователем struct Container
- это скрыть его реализацию . Это напоминает идиому C ++ Pimpl: У пользователя нет заголовка, который фактически определяет Container
, но есть только заголовок, определяющий операций над ним, которые принимают и возвращают указатели на Container
(как ваш init()
). Container
остается неполным типом для пользователя.
Вот пример. Эта версия контейнера имеет следующие функции:
Он не обеспечивает прямой доступ к данным, а вместо этого раздает копии данных. Является ли это приемлемыми накладными расходами, зависит от варианта использования. Я просто хотел подчеркнуть, что нам нужно нулевое знание Контейнера. Он полностью скрыт. За исключением реинжиниринга типа, вообще невозможно манипулировать содержимым, кроме как через его официальный интерфейс. (Это может быть недостатком.)
Фактический буфер (и, следовательно, размер) теперь динамический c. Единственное ограничение на размер пользовательских данных устанавливается системой.
Контейнер выделяет память для пользователя, когда пользователь получает копию данных в контейнере, аналогично POSIX scanf
"символ назначения-выделения" 'm'.
Контейнер поддерживает отдельные размеры для объема выделенной памяти и того, сколько фактически занято пользовательскими данными. Это позволяет избежать ненужных перераспределений.
Пользователь видит контейнер как этот заголовок с набором сигнатур функций:
#ifndef CONTAINER_INTERFACE_H
#define CONTAINER_INTERFACE_H
/* An abstract container. It can hold arbitrary amounts of data
by means of danamic allocation. An out-of-memory condition will make
it exit with an exit code of 1.
*/
#include <stddef.h> // size_t
/** Forward declaration, actual definition unknown */
struct Container;
typedef struct Container Container; // convenience
/** Create and initialize a Container of size 0.
*/
Container *ac_init();
/** Delete a Container and free its buffer */
void ac_dispose(Container *container);
/** Obtain the data in the given container. Note that we don't
expose the internal pointer to the user.
@param userBuf is a pointer a pointer
which will be set to an allocated memory area of sufficient
size. The user must free() it. If the container does not hold data,
*userBuf is not changed.
@return the number of bytes actually copied, which is also the
size of the allocated buffer.
*/
size_t ac_get(Container *container, unsigned char **userBuf);
/** Fill the container buffer with user data.
@return the number of bytes actually copied
*/
void ac_put(Container *container, const unsigned char *userData, size_t userDataSz);
/* ... (Many) more functions for more complicated structs */
#endif //ndef CONTAINER_INTERFACE_H
Простой пример использования:
#include <stdio.h>
#include <stdlib.h> // exit, malloc etc.
#include <string.h>
#include "container-interface.h"
/// Obtain a copy of the container data and print it.
void printContainerData(Container *c)
{
unsigned char *dataFromContainer; // will be set by ac_get
size_t contDataSz = ac_get(c, &dataFromContainer);
if(contDataSz == 0)
{
printf("[empty]\n");
}
else
{
dataFromContainer[contDataSz-1] = 0; // terminate string just in case.
printf("String from container: ->%s<-\n", (const char *)dataFromContainer);
free(dataFromContainer);
}
}
int main()
{
char *userInput; // will be set by scanf
Container *c = ac_init();
while(1) // exit by EOF (Ctrl-Z or Ctrl-D)
{
printf("Please enter a line (empty for exit) ->");
// EOF etc. will make scanf return something other than 1.
// Use the fancy "m" POSIX extension in the format string
// which allocates memory for us, obviating maximum line length
// considerations.
if(scanf("%m[^\n]", &userInput) != 1) { break; }
getchar(); // read away remaining newline
ac_put(c, (unsigned char *)userInput, strlen(userInput)+1);
printContainerData(c);
free(userInput);
}
ac_dispose(c); // kinda unnecessary in a hosted environment, but good habit.
}
Последний (скрытый, как правило, в библиотеке, которая связана только с) реализация контейнера и его члена "Функции выглядят так:
#include <stdlib.h> // exit, malloc etc.
#include <string.h> // memcpy
#include "container-interface.h" // to make sure the function signatures match
/** The actual definition of Container. The user never sees this. */
struct Container
{
unsigned char *buf;
size_t dataSz;
size_t allocSz;
};
/** Create and initialize a struct Container */
struct Container *ac_init()
{
struct Container *newCont = malloc(sizeof(struct Container));
if(!newCont) { exit(1); } // out of mem
newCont->dataSz = 0;
newCont->allocSz = 0;
newCont->buf = NULL;
return newCont;
}
void ac_dispose(struct Container *container)
{
free(container->buf);
free(container);
}
size_t ac_get(struct Container *container, unsigned char **userBuf)
{
if(container->dataSz > 0)
{
*userBuf = malloc(container->dataSz);
if(!*userBuf) { exit(1); } // out of mem
memcpy(*userBuf, container->buf, container->dataSz);
}
return container->dataSz;
}
void ac_put(struct Container *container, const unsigned char *userData, size_t userDataSz)
{
if(userDataSz != 0)
{
if(container->allocSz < userDataSz)
{
free(container->buf);
container->buf = malloc(userDataSz);
if(!container->buf) { exit(1); } // out of mem
container->allocSz = userDataSz;
}
memcpy(container->buf, userData, userDataSz);
}
container->dataSz = userDataSz;
}
/* ... (Many) more functions for more complicated structs */