Конструктор, деструктор и автоматический стек в C++

С удивлением узнал об интересном способе использования стека в нестандартных целях. Посмотрите внимательно на код ниже.

Его фишка в том, что класс Sample в конструкторе сохраняет предыдущий инстанс типа Sample, а в деструкторе — восстанавливает. При этом метод Sample::instance() всегда будет возвращать текущий объект типа Sample.

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

class Sample
{
public:
    Sample()
        : prevInst_s(curInst_s)
    {
        curInst_s = this;
    }

    ~Sample()
    {
        curInst_s = prevInst_s;
    }

    static Sample *instance()
    {
        return curInst_s;
    }

private:
    Sample *prevInst_s;
    static Sample *curInst_s;
};

Sample *Sample::curInst_s = NULL;
C++

Пример использования такого функционала под катом. (в комментариях указано — какой instance сейчас текущий)

#include <stdio.h>

class Sample
{
public:
    Sample(const char *name)
        : name_(name)
        , prevInst_s(curInst_s)
    {
        curInst_s = this;
        printf("current: %s\n", name_);
    }

    ~Sample()
    {
        curInst_s = prevInst_s;

        if (curInst_s)
        {
            printf("restore: %s\n", curInst_s->name_);
        }
    }

    static Sample *instance()
    {
        return curInst_s;
    }

private:
    const char *name_;
    Sample *prevInst_s;
    static Sample *curInst_s;
};

Sample *Sample::curInst_s = NULL;


int main()
{
    /*
     *
     * Sample::instance() => NULL
     *
     */
    {
        Sample sampleA("A");
        /*
         *
         * Sample::instance() => A
         *
         */
        {
            Sample sampleB("B");
            /*
             *
             *  Sample::instance() => B
             *
             */
        }
        /* 
         * 
         * Sample::instance() => A
         *
         */
        {
            Sample sampleC("C");
            /*
             *
             * Sample::instance() => C
             *
             */
            {
                Sample sampleD("D");
                /*
                 *
                 * Sample::instance() => D
                 *
                 */
            }
            /*
             *
             * Sample::instance() => C
             *
             */
        }
        /*
         *
         * Sample::instance() => A
         *
         */
    }
    /*
     *
     * Sample::instance() => NULL
     *
     */
}
C++
Линейная интерполяция между углами

Иногда бывает необходимо выполнить линейную интерполяцию между углами. Ситуация осложняется тем, что углы в градусах бывают равны при совершенно разных значениях (например угол -360, 360 и 0 на самом деле одно и тоже). Делюсь методом правильной интерполяции между углами (подсмотрено в исходниках Quake).

float lerpAngle(float a1, float a2, float t)
{
    if (a1 - a2 > 180) 
    {
        a1 -= 360;
    }
    if (a1 - a2 < -180)
    {
        a1 += 360;
    }
    return a1 + t * (a2 - a1);
}
C

Если вам необходимо вычислить расстояние в градусах между углами, то можно переписать код так:

float distAngle(float a1, float a2)
{
    if (a1 - a2 > 180) 
    {
        a1 -= 360;
    }
    if (a1 - a2 < -180)
    {
        a1 += 360;
    }
    return abs(a2 - a1);
}
C
Простыми словами о представлении форматов float32 и double64 в памяти компьютера

Форматы float32 и double64 на пальцах

Вкратце, идея довольна простая: исходное число необходимо привести к нормализованному виду <nobr>1.NNN<sub>2</sub></nobr> в двоичной системе счисления с помощью битового сдвига, затем записать дробную часть этого числа в мантиссу, а количество сдвигов в экспоненту. И завершающим штрихом сохранить знак исходного числа.

Формат float32 имеет такой вид: <span class="text-red">1s</span><span class="text-green">8e</span><span class="text-blue">23m</span>, где s — количество бит под знак, e — экспонента, m — мантисса. Для формата double64 вид такой — <span class="text-red">1s</span><span class="text-green">11e</span><span class="text-blue">52m</span>.

Первый бит кодирует знак числа. Если это ноль, число положительно, в противном случае число отрицательное.

Затем идут восемь бит экспоненты. Если совсем простыми словами, то экспонента — это количество сдвигов запятой в исходном числе, представленном в двоичном виде, для получения нормализованного числа вида <nobr>1.NNN<sub>2</sub></nobr>. Чтобы получить количество сдвигов из этих восьми бит надо отнять <nobr>127<sub>10</sub></nobr>. Отрицательная экспонента — сдвиг влево, положительная — сдвиг вправо.

После экспоненты следуют 23 бита мантиссы. Это дробная часть числа <nobr>1.NNN<sub>2</sub></nobr>.

А теперь практическая часть!

Перевод десятичной дроби во float32

Попробуем с полученными знаниями закодировать число <nobr>-2.625<sub>10</sub></nobr>.

В двоичном виде это число имеет вид <nobr>10.101<sub>2</sub></nobr>. Для получения нормализованного числа необходимо запятую сдвинуть влево на один разряд. Получим число <nobr>1.0101<sub>2</sub></nobr> и экспоненту равной 1.

Для сохранения экспоненты к ней надо прибавить <nobr>127<sub>10</sub></nobr>. Получится <nobr><span class="text-green">126</span><sub>10</sub></nobr> или <nobr><span class="text-green">01111110</span><sub>2</sub></nobr> в двоичном виде.

Берем нормализованное число <nobr>1.<b>0101</b><sub>2</sub></nobr> и выделяем мантиссу <nobr><b>0101</b><sub>2</sub></nobr>. Для сохранения этого числа, которое занимает 4 бита, надо добавить нули справа до 23 бит. Получится число <nobr><span class="text-blue"><b>0101</b>0000000000000000000</span><sub>2</sub></nobr>.

Знак числа отрицательный, значит первый бит равен единице.

Итог: s=<nobr><span class="text-red">1</span><sub>2</sub></nobr> e=<nobr><span class="text-green">01111110</span><sub>2</sub></nobr> m=<nobr><span class="text-blue">01010000000000000000000</span><sub>2</sub></nobr>, с чем я вас и поздравляю.

Обратный перевод: из float32 в число

Разберем пример из википедии. Есть число во float32 <nobr>0xC0000000<sub>16</sub></nobr>. В двоичной системе это будет <span class="text-red">1</span><span class="text-green">10000000</span><span class="text-blue">00000000000000000000000</span><sub>2</sub>.

Разобъем его на компоненты: s=<nobr><span class="text-red">1</span><sub>2</sub></nobr> e=<nobr><span class="text-green">10000000</span><sub>2</sub></nobr> m=<nobr><span class="text-blue">00000000000000000000000</span><sub>2</sub></nobr>.

Мантисса равна нулю, но, как уже было сказано выше, сохраняется только дробная часть мантиссы, а единица отбрасывается. Значит мантисса равна <nobr>1.<span class="text-blue">00000000000000000000000</span><sub>2</sub></nobr>.

Экспонента равна <nobr><span class="text-green">10000000</span><sub>2</sub></nobr> или <nobr><span class="text-green">128</span><sub>10</sub></nobr>, отнимаем <nobr>127<sub>10</sub></nobr> и получается, что экспонента равна единице.

Возьмем мантиссу и сдвинем точку вправо на эту единицу, получится <nobr>10.0000000000000000000000<sub>2</sub></nobr>, это <nobr>2<sub>10</sub></nobr> в десятичной системе счисления.

Знак числа равен единице, значит исходное число отрицательное.

Решение: <nobr>-2<sub>10</sub></nobr>, что и требовалось доказать.

P.S. Маленькие циферки справа от числа обозначают систему счисления, если кто не знает. Пример: два в десятичной системе — 210, один‑ноль‑один в двоичной системе — 1012. Кстати, красным цветом выделен знак числа, зелёным — экспонента, а мантисса, соответственно, синим.

Полезные ссылки

Решение проблемы с нехваткой памяти при загрузке текстур в Android

Как известно, в популярной нынче мобильной ОС Android есть фатальный недостаток — это язык Java. Более того, каждому приложению выделяется кусок памяти размером от 32-х мегабайт до бесконечности в зависимости от желания производителя мобильного телефона.

Если вы делаете большое приложение с использованием OpenGLES и пачкой больших текстур размером 2048×2048 (а это 16 мегабайт для формата RGBA), то памяти для загрузки у вас не будет. После загрузки второй-третьей, пятой текстуры с помощью BitmapFactory приложение упадет с OutOfMemoryError, что есть очень плохо.

Хочу поделиться решением этой проблемы. Она состоит из двух пунктов: загружать изображения из PNG с помощью libpng и исключить работу с большими блоками данных из Java части.

Решение первой части и исходники для прямой загрузки изображения из PNG файла можно найти на stackoverflow.com.

Для работы с памятью создадим вот такой Java класс:

package com.example.memory;

import java.nio.IntBuffer;

public class MemoryManager {
    public static native IntBuffer create(int sizeInBytes);


    public static native void release(IntBuffer buffer);
}
Java

И нативную часть (язык cpp):

#include <jni.h>

#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jobject JNICALL Java_com_example_memory_MemoryManager_create(
    JNIEnv *env,
    jclass cls,
    jint sizeInBytes)
{
    // Выделяем блок памяти
    void *buf = malloc(sizeInBytes);
    // Создаем буфер 
    return env->NewDirectByteBuffer(buf, 0);
}

JNIEXPORT void JNICALL Java_com_example_memory_MemoryManager_release(
    JNIEnv *env,
    jclass cls,
    jobject buffer)
{
    // Освобождаем память
    free(env->GetDirectBufferAddress(buffer));
}
#ifdef __cplusplus
}
#endif
C++

Далее алгоритм работы такой:

  1. Читаем высоту и ширины изображения из первых 24 байт PNG (подробности в статье на хабре: Получаем тип и размеры изображения без скачивания его целиком, используя Python)
  2. Выделяем IntBuffer с помощью MemoryManager размером W*H*4 (32bit RGBA, 4 байта на пиксель)
  3. Считываем в него PNG файл
  4. Загружаем эти данные в текстуру с помощью glTexImage2d
  5. Освобождаем IntBuffer в MemoryManager

Честно говоря, я слабо понимаю — почему в нативе вызывается функция NewDirectByteBuffer, а в Java приходит IntBuffer, а не ByteBuffer. Загадка какая-то.

UPD: Если вы решили пойти стандартным путем с загрузкой Bitmap и GLUtil.texImage2D, то не забывайте вызывать bitmap.recycle() после загрузки изображения в текстуру, дабы освободить нативную память.

Самая маленькая программа на C/C++, которая вызывает segmentation fault

Самый маленький код, который после компиляции с помощью gcc вызывает segmentation fault:

main;
C

Компилируем:

$ gcc crash.c
crash.c:1: warning: data definition has no type or storage class
Bash

Запускаем:

$ ./a.out
Segmentation fault
Bash

Увидел не так давно на хабре, делюсь.

Блог Евгения Жирнова