Многие объяснения уже присутствуют, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать рекомендациям, чтобы избежать NullPointerException
вообще.
См. также: A хороший список лучших практик
Я бы добавил, очень важно, хорошо использовать модификатор final
. Использование "окончательной" модификатор, когда это применимо в Java
Сводка:
final
для обеспечения хорошей инициализации. @NotNull
и @Nullable
if("knownObject".equals(unknownObject)
valueOf()
поверх toString (). StringUtils
StringUtils.isEmpty(null)
. реализация alloca
на самом деле требует помощи компилятора. Несколько человек здесь говорят, что это столь же легко как:
sub esp, <size>
который является, к сожалению, только половиной изображения. Да, который "выделил бы место на стеке", но существует несколько глюков.
если компилятор испустил код, который ссылается на другие переменные относительно esp
вместо ebp
(типичный, если Вы компилируете без указателя кадра). Затем те ссылки должны быть скорректированы. Даже с указателями кадра, компиляторы иногда делают это.
что еще более важно, по определению, место, выделенное с alloca
должен быть "освобожден", когда функция выходит.
Большой является точкой № 2. Поскольку Вам нужен компилятор для испускания кода для симметричного добавления <size>
кому: esp
в каждой точке выхода функции.
Наиболее вероятный случай является предложениями компилятора некоторые intrinsics, которые позволяют устройствам записи библиотеки просить у компилятора необходимую помощь.
Править:
На самом деле, в glibc (реализация GNU libc). Реализация alloca
просто это:
#ifdef __GNUC__
# define __alloca(size) __builtin_alloca (size)
#endif /* GCC. */
Править:
после размышления об этом требовался бы минимум, которому я верю, будет, чтобы компилятор всегда использовал указатель кадра в любых функциях, который использует alloca
, независимо от настроек оптимизации. Это позволило бы всем местным жителям ссылаться через ebp
безопасно и очистка кадра была бы обработана путем восстановления указателя кадра на esp
.
Править:
Таким образом, я сделал некоторое экспериментирование с вещами как это:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define __alloca(p, N) \
do { \
__asm__ __volatile__( \
"sub %1, %%esp \n" \
"mov %%esp, %0 \n" \
: "=m"(p) \
: "i"(N) \
: "esp"); \
} while(0)
int func() {
char *p;
__alloca(p, 100);
memset(p, 0, 100);
strcpy(p, "hello world\n");
printf("%s\n", p);
}
int main() {
func();
}
который, к сожалению, не работает правильно. После анализа блока, произведенного gcc. Кажется, что оптимизация мешает. Проблема, кажется, что, так как оптимизатор компилятора полностью не знает о моем встроенном ассемблерном коде, он имеет привычку к выполнению вещей в неожиданном порядке и все еще ссылке на вещи через esp
.
Вот результирующий ASM:
8048454: push ebp
8048455: mov ebp,esp
8048457: sub esp,0x28
804845a: sub esp,0x64 ; <- this and the line below are our "alloc"
804845d: mov DWORD PTR [ebp-0x4],esp
8048460: mov eax,DWORD PTR [ebp-0x4]
8048463: mov DWORD PTR [esp+0x8],0x64 ; <- whoops! compiler still referencing via esp
804846b: mov DWORD PTR [esp+0x4],0x0 ; <- whoops! compiler still referencing via esp
8048473: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048476: call 8048338 <memset@plt>
804847b: mov eax,DWORD PTR [ebp-0x4]
804847e: mov DWORD PTR [esp+0x8],0xd ; <- whoops! compiler still referencing via esp
8048486: mov DWORD PTR [esp+0x4],0x80485a8 ; <- whoops! compiler still referencing via esp
804848e: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048491: call 8048358 <memcpy@plt>
8048496: mov eax,DWORD PTR [ebp-0x4]
8048499: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
804849c: call 8048368 <puts@plt>
80484a1: leave
80484a2: ret
Как Вы видите, это не настолько просто. К сожалению, я поддерживаю свое исходное утверждение, что Вам нужна помощь компилятора.
Это было бы хитро, чтобы сделать это - на самом деле, если Вы не имеете достаточно контроля над генерацией кода компилятора, это не может быть сделано полностью безопасно. Ваша стандартная программа должна была бы управлять стеком, таким, что, когда это возвратилось, все было убрано, но указатель вершины стека остался в таком положении, которым блок памяти остался в том месте.
Проблема состоит в том, что, если Вы не можете сообщить компилятору, что указатель вершины стека, был изменен через Ваш вызов функции, он может решить, что он может продолжить относиться к другим местным жителям (или безотносительно) через указатель вершины стека - но смещения будут неправильными.
Для языка программирования D исходный код для alloca () идет с загрузкой. То, как это работает, довольно хорошо прокомментировано. Для dmd1 это находится в/dmd/src/phobos/internal/alloca.d. Для dmd2 это находится в/dmd/src/druntime/src/compiler/dmd/alloca.d.
C и стандарты C++ не указывают это alloca()
имеет к использованию стек, потому что alloca()
не находится в C или стандартах C++ (или POSIX в этом отношении) ¹.
Компилятор может также реализовать alloca()
использование "кучи". Например, ARM RealView (RVCT) компилятор alloca()
использование malloc()
выделить буфер (ссылаемый на их веб-сайте здесь), и также заставляет компилятор испускать код, который освобождает буфер, когда функция возвращается. Это не требует проигрывания с указателем вершины стека, но все еще требует поддержки компилятора.
Microsoft Visual C++ имеет a _malloca()
функция, которая использует "кучу", если нет достаточного количества комнаты на стеке, но это требует, чтобы вызывающая сторона использовала _freea()
, в отличие от этого, _alloca()
, который не нуждается/хочет в явном освобождении.
(С деструкторами C++ в Вашем распоряжении, можно, очевидно, сделать очистку без поддержки компилятора, но Вы не можете объявить локальные переменные в произвольном выражении, таким образом, я не думаю, что Вы могли записать alloca()
макрос, который использует RAII. С другой стороны по-видимому, Вы не можете использовать alloca()
в некоторых выражениях (как параметры функции) так или иначе.)
¹ Да, законно записать alloca()
это просто звонит system("/usr/games/nethack")
.
alloca непосредственно реализован в ассемблерном коде. Поэтому Вы не можете управлять расположением стека непосредственно с высокоуровневых языков.
Также обратите внимание, что большая часть реализации выполнит некоторую дополнительную оптимизацию как выравнивание стека по причинам производительности. Стандартный способ выделить стековое пространство на X86 похож на это:
sub esp, XXX
Принимая во внимание, что XXX число байтов к allcoate
Править:
Если Вы хотите посмотреть на реализацию (и Вы используете MSVC), см. alloca16.asm и chkstk.asm.
Код в первом файле в основном выравнивает желаемый размер выделения к 16-байтовой границе. Код в 2-м файле на самом деле обходит все страницы, которые принадлежали бы новой области стека и касаются их. Это возможно инициирует исключения PAGE_GAURD, которые используются ОС для роста стека.
Можно исследовать источники компилятора C с открытым исходным кодом, как Открывают Watcom и находят его сами
Alloca легок, Вы просто перемещаете указатель вершины стека вверх; затем генерируйте все чтение-записи для указания на этот новый блок
sub esp, 4
Я рекомендую "ввести" инструкцию. Доступный на 286 и более новые процессоры (возможно, было доступно на 186 также, я не могу помнить бесцеремонно, но это не было широко доступно так или иначе).