Это одно из самых запутанных сообщений об ошибках, которые каждый программист VC ++ видел снова и снова. Давайте сначала сделаем чёткость.
A. Что такое символ? Короче говоря, символ - это имя. Это может быть имя переменной, имя функции, имя класса, имя typedef или что-либо кроме тех имен и знаков, которые принадлежат языку C ++. Он определяется пользователем или вводится библиотекой зависимостей (другой пользовательский).
B. Что является внешним? В VC ++ каждый исходный файл (.cpp, .c и т. Д.) Рассматривается как единица перевода, компилятор компилирует по одному модулю за раз и генерирует один объектный файл (.obj) для текущей единицы перевода. (Обратите внимание, что каждый заголовочный файл, включенный в этот исходный файл, будет предварительно обработан и будет рассматриваться как часть этой единицы перевода). Все внутри единицы перевода считается внутренним, все остальное считается внешним. В C ++ вы можете ссылаться на внешний символ, используя ключевые слова, такие как extern
, __declspec (dllimport)
и т. Д.
C. Что такое «решимость»? Resolve - термин времени связывания. Во время компоновки линкер пытается найти внешнее определение для каждого символа в объектных файлах, которые не могут найти свое определение внутри. Объем этого процесса поиска, включая:
Этот процесс поиска называется разрешением.
D. Наконец, почему Unresolved External Symbol? Если компоновщик не может найти внешнее определение для символа, который не имеет определения внутри, он сообщает об ошибке неразрешенного внешнего символа.
E. Возможные причины LNK2019: ошибка неразрешенного внешнего символа. Мы уже знаем, что эта ошибка связана с тем, что компоновщик не смог найти определение внешних символов, возможные причины могут быть отсортированы как:
Например, если у нас есть функция foo, определенная в a.cpp:
int foo()
{
return 0;
}
В b.cpp мы хотим вызвать функцию foo, поэтому добавим
void foo();
, чтобы объявить функцию foo () и вызвать ее в другом теле функции, скажем bar()
:
void bar()
{
foo();
}
Теперь, когда вы создаете этот код, вы получите ошибку LNK2019, жалуясь, что foo является неразрешенным символом , В этом случае мы знаем, что foo () имеет свое определение в a.cpp, но отличается от того, которое мы вызываем (другое возвращаемое значение). Это означает, что определение существует.
Если мы хотим вызвать некоторые функции в библиотеке, но библиотека импорта не добавлен в дополнительный список зависимостей (установленный из: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
) вашего проекта. Теперь компоновщик сообщит LNK2019, поскольку определение не существует в текущей области поиска.
malloc()
и free()
зависят от системы / компилятора, поэтому трудно дать конкретный ответ.
Дополнительная информация по этому другому вопросу .
В соответствующей записке библиотека GLib имеет функции распределения памяти, которые не сохраняют неявный размер, - и затем вы просто передаете параметр размера бесплатно. Это может исключить часть служебных данных.
память, выделенная с помощью malloc () или calloc () или realloc ().
void free (void * ptr);
свободная функция не принимает размер в качестве параметра , Как функция free () знает, сколько памяти освобождается от заданного только указателя?
Ниже приведен наиболее распространенный способ хранения размера памяти, чтобы free () знал, что размер памяти будет освобожден.
Когда выделение памяти выполняется, выделенное пространство кучи на одно слово больше, чем запрошенная память. Дополнительное слово используется для хранения размера выделения и позже используется функцией free ()
Из списка comp.lang.c
часто задаваемых вопросов: Как бесплатно узнать, сколько байтов освободится?
Реализация malloc / free запоминает размер каждого блока по мере его выделения , поэтому нет необходимости напоминать о размере при освобождении. (Как правило, размер хранится рядом с выделенным блоком, поэтому обычно обычно плохо ломаются, если границы выделенного блока даже немного перешагнуты)
Чтобы ответить на вторую половину вопроса: да, вы можете, и довольно общий шаблон в C следующий:
typedef struct {
size_t numElements
int elements[1]; /* but enough space malloced for numElements at runtime */
} IntArray_t;
#define SIZE 10
IntArray_t* myArray = malloc(sizeof(intArray_t) + SIZE * sizeof(int));
myArray->numElements = SIZE;
Большинство реализаций функций распределения памяти C будут хранить учетную информацию для каждого блока, как в строке, так и отдельно.
Один типичный способ (в строке) состоит в том, чтобы фактически распределять как заголовок, так и память вы просили, добрались до минимального размера. Например, если вы запросили 20 байтов, система может выделить 48-байтовый блок:
Адрес, указанный вами, является адресом данных площадь. Затем, когда вы освободите блок, free
просто возьмет адрес, который вы ему даете, и, предположив, что вы не наполнили этот адрес или память вокруг него, проверьте учетную информацию непосредственно перед этим. Графически это будет выглядеть так:
____ The allocated block ____
/ \
+--------+--------------------+
| Header | Your data area ... |
+--------+--------------------+
^
|
+-- The address you are given
Имейте в виду, что размер заголовка и отступов полностью определяется реализацией (на самом деле, все это определяется реализацией (a), но встроенный учетный параметр является общим).
Контрольные суммы и специальные маркеры, которые существуют в учетной информации, часто являются причиной ошибок, таких как «Memory arena corrupted» или «Double free», если вы перезаписываете их или освободить их дважды.
Отступы (чтобы сделать выделение более эффективным) - это то, почему вы иногда можете писать немного за пределы запрашиваемого пространства, не вызывая проблем (все же, не делайте этого, это undefined behavior и, как раз потому, что он работает иногда, не означает, что это нормально).
(a) Я написал реализации malloc
во встроенных системах, где вы получили 128 байты независимо от того, что вы просили (это был размер самой большой структуры в системе), если вы попросили 128 байт или меньше (запросы на большее должно быть выполнено с возвратным значением NULL). Очень простая битовая маска (т. Е. Не в строке) использовалась для определения того, был ли выделен 128-байтовый кусок.
Другие, которые я разработал, имели разные пулы для 16-байтовых фрагментов, 64-байтовые фрагменты, 256-байтовые фрагменты и 1K-фрагменты, снова используя битовую маску для определения того, какие блоки были использованы или доступны.
Обеим этим опциям удалось уменьшить накладные расходы бухгалтерской информации и увеличить скорость malloc
и free
(нет необходимости объединять соседние блоки при освобождении), что особенно важно в среде, в которой мы работали.
Менеджер кучи сохранил объем памяти, принадлежащий выделенному блоку, где-то, когда вы вызвали malloc
.
Я никогда не реализовал его сам, но я думаю, что память прямо перед выделенным блоком может содержат метаинформацию.
Когда мы вызываем malloc, он просто потребляет больше байта от его требования. Это больше байтового потребления содержит информацию, такую как контрольная сумма, размер и другая дополнительная информация. Когда мы звоним бесплатно в это время, он напрямую переходит к этой дополнительной информации, где он находит адрес, а также определяет, сколько блоков будет бесплатным.