Является ли calloc (4, 6) таким же, как calloc (6, 4)? - PullRequest
35 голосов
/ 02 февраля 2009

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

Если они одинаковые, почему бы не взять вместо этого один аргумент?

Ответы [ 7 ]

21 голосов
/ 02 февраля 2009

Люди в основном используют процедуры выделения для выделения пространства для заданного количества элементов, поэтому calloc() позволяет точно указать это. Так, например, если вы хотите пространство для 100 целых чисел или 20 вашей собственной структуры:

int *pInt = calloc (100, sizeof(int));
tMyStruct *pMyStruct = calloc (20, sizeof(tMyStruct));

Этот код на самом деле выглядит немного «лучше», чем эквивалентные malloc() вызовы:

int *pInt = malloc (100 * sizeof(int));
tMyStruct *pMyStruct = malloc (20 * sizeof(tMyStruct));

хотя для опытных C-кодеров нет никакого реального различия (кроме нулевой инициализации курса).

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

12 голосов
/ 02 февраля 2009

calloc (4, 6) и calloc (6, 4) равны НЕ одинаково:

В типичной 32-битной / 64-битной системе первая выделяет 32 байта, а вторая - 24 байта.


void *calloc(size_t nelem, size_t elsize);

Ключевым моментом является то, что calloc должен возвращать память, как если бы она была правильно выровнена как массив. Он предназначен для выделения массива и используется следующим образом:

A *p = (A*)calloc(count, sizeof(A));
for (int i = 0; i < count; ++i)
{
    f(&(p[i]));
    // f(p + i) is also valid
}

или

A *p = (A*)calloc(count, sizeof(A));
for (A *q = p; q != p + count; ++q)
{
    f(q);
}

calloc должен распределять массив с учетом заполнения и других эксплуатационных требований целевой системы. Таким образом, на большинстве 32-битных машин, где 6-байтовая структура должна быть дополнена до 8 байтов, она будет выделять 4 лота по 8 байтов.

calloc, где первый аргумент - sizeof (), скорее всего, ошибка, и ее следует изучить.

calloc, где второй аргумент не sizeof (atype) не определен. Он пахнет скрытыми предположениями и опасен для порта.

Пояснение: В типичной 32-битной / 64-битной системе структура, вероятно, будет дополнена и выровнена по кратности 32-битной. Таким образом, в этих системах размер не вернет 6 байтов. На самом деле нет никакой гарантии, что компилятор не дополнит и не выровняет какой-то кратный 16 байт, если этого требует компилятор / платформа.

Мой ответ основан на том факте, что вы не должны делать предположений о размере конструкции. Они могут меняться в зависимости от параметров компилятора или целевой платформы. Просто убедитесь, что ваш второй аргумент является выражением sizeof и не делает предположений .


Из стандарта :

Функция calloc () должна выделять неиспользуемое пространство для массива элементов nelem, каждый из которых имеет размер elsize в байтах. Пробел должен быть инициализирован для всех битов 0.

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

11 голосов
/ 02 февраля 2009

К отличным опубликованным ответам я хочу добавить еще одну разницу между использованием calloc(nelem, elsize) против malloc(nelem * elsize): качественные реализации calloc гарантируют, что если ваши nelem и elsize будут достаточно большими, чтобы вызвать целочисленное переполнение при умножении вместе, это приведет к сбою, а не к уменьшенному выделению, как наивный вызов malloc.

Только одной этой функции мне было бы достаточно, чтобы я предпочел calloc malloc. Фоновое чтение.

5 голосов
/ 02 февраля 2009

Несмотря на принятый ответ (который я считаю правильным), кажется, что существует путаница относительно того, сколько байтов выделено из-за выравнивания. Итак, вот небольшой тест на моем 32-битном Linux с gcc-4.3:

#include <stdio.h>
#include <stdlib.h>

int main()
{
  char* p1 = calloc(6, 4);
  char* p2 = calloc(4, 6);
  char* p3 = calloc(1,1);
  printf("%p, %p, %p\n", p1, p2, p3);
  return 0;
}

Результат:

0x826b008, 0x826b028, 0x826b048

, который показывает, что и calloc(6,4), и calloc(4,6) выделяют одинаковый объем памяти, который округляется до 32 байт в моей системе. Изменение чисел на calloc(3,4) и calloc(4,3) даст следующий результат:

0x95d2008, 0x95d2018, 0x95d2028

, который показывает, что 16 байтов зарезервированы, когда 12 запрошены и выделены для программы. В любом случае вызовы calloc(a,b) и calloc(b,a) одинаково влияют на использование памяти.


Добавлено Джонатаном Леффлером, потому что 300 символов никогда не будет достаточно.

Рассмотрим эту программу, которая пропускает память как настоящее сито, но демонстрирует точку:

#include <stdlib.h>
#include <stdio.h>

int main()
{
    int i, j, k;

    for (i = 1; i < 17; i++)
        for (j = 1; j < 9; j++)
            for (k = 0; k < 4; k++)
                printf("(%2d,%d)%d: %p\n", i, j, k, calloc(i, j));
    return(0);
}

В Windows, под Cygwin, это начинается с выделения блоков, которые находятся на расстоянии 16 байтов (фактически, второй блок составляет 24 байта после первого, но после этого они на расстоянии 16 байтов). При выделении (2,7) адреса блоков начинают увеличиваться на 24 байта; аналогично, (3,4) распределяет блоки по 16 байтов, но (3,5) распределяет блоки по 24 байта. И, к сведению, оба (4,6) и (6,4) возвращают указатели на расстоянии 32 байта.

Это просто демонстрирует, что с вызовом выделения связаны некоторые издержки. Если вы посмотрите на архетипическую реализацию malloc () и др. В K & R, то увидите, что размер блока хранится впереди памяти, которую вы имеете право использовать. Различные реализации делают эти вещи по-разному; Те, кто беспокоится о растоптании памяти, избегают хранения контрольных данных рядом с местом, где пользователь может нанести ущерб.

Когда вы используете calloc (4,6), вы имеете надежный доступ только к 24 байтам данных. Даже если ваша реализация выдает возвращаемые значения с интервалом в 32 байта, вы не можете безопасно использовать больше, чем 24 запрошенных вами байта. А отладочные версии malloc () будут наблюдать, если вы напишете за запрошенные вами границы.

4 голосов
/ 02 февраля 2009

Существует небольшое различие: Calloc может решить обнулить память только по мере необходимости, и тогда есть преимущество, зная размер элементов.

Я не могу назвать ни одной реализации, делающей это, но это было задумано.

Как пример:

Один выделяет 4 ГБ памяти, но система имеет только 2 ГБ: не имеет смысла записывать 2 ГБ нулевой памяти в виртуальную память, поэтому система может установить флаг грязной памяти для этой памяти, чтобы обнулить ее загружается в память.

3 голосов
/ 02 февраля 2009

То же самое. Причина в том, что большую часть времени вы хотите использовать оператор sizeof в качестве одного из аргументов. Если передача двух параметров вас беспокоит, вызовите malloc(), который имеет один аргумент.

1 голос
/ 01 февраля 2016

Есть еще один способ разобраться в этом вопросе.

Библиотека GNU C определяет calloc следующим образом:

void * __libc_calloc (size_t n, size_t elem_size)
{
  // ... (declarations)

  /* size_t is unsigned so the behavior on overflow is defined.  */
  bytes = n * elem_size;
#define HALF_INTERNAL_SIZE_T \
  (((INTERNAL_SIZE_T) 1) << (8 * sizeof (INTERNAL_SIZE_T) / 2))
  if (__builtin_expect ((n | elem_size) >= HALF_INTERNAL_SIZE_T, 0))
    {
      if (elem_size != 0 && bytes / elem_size != n)
        {
          __set_errno (ENOMEM);
          return 0;
        }
    }

  void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    {
      sz = bytes;
      mem = (*hook)(sz, RETURN_ADDRESS (0));
      if (mem == 0)
        return 0;

      return memset (mem, 0, sz);
    }

  sz = bytes;

  // ...more stuff, but no mention of n & elem_size anymore
}

Итак, по крайней мере в glibc эти два вызова имеют одинаковый эффект.

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