4

SmartPointers для C++ в стиле Java

Не так давно я перешел с плюсов на Java. Согласен с автором поста в том, что чисто по-человечески писать на Java приятней, чем на C++. Страхов меньше, язык проще, в стандартной библиотеке есть почти все, что нужно. Одно мне не нравится - все же для любой проги на Java необходимо окружение, а 17 МБ (для JRE 5) не каждый готов скачать ради того, чтобы запустить простенькую програмку. Хочется мне иметь в C++ безопасность и удобство Java.
В процессе мозголомства и раздумий на тему - как же в принципе реализуется сборщик мусора( он же garbage collector - GC) и лазанью по инету пришел к одному результату... А именно: в C++ можно создать SmartPointer, пользоваться которым можно абсолютно так же, как в Java пользуются ссылками на объекты. И поведение его будет таким же, как поведение ссылок в Java. При этом GC как таковой даже и не нужен - его функции будет выполнять сам SmartPointer.

Как ведут себя ссылки в Java? Через них можно обращаться к полям/методам класса, присваивать ссылкам другие ссылки совместимых типов, выполнять проверку на равенство, делать ссылку "инвалидной" (т.е. присвоить ей значение null). По выходе ссылки из области видимости память, связанная с этой сылкой, может быть освобождена сборщиком мусора, при условии что эта ссылка не была передана куда-то еще. Вот, пожалуй и все.
Итак, если в нашей программе будут только такие SmartPointer-ы и не будет других видов указателей, про ручное освобождение памяти и утечку памяти можно будет забыть. Конечно, можно умудрится сделать циклические ссылки (и на Java это сделать проще, чем на C++) В таких случаях, конечно, придется освобождать память вручную, но не через delete, а просто присваивая ссылке значение NULL, что гораздо безопаснее (можно присваивать одной и той же ссылке NULL сколько влезет, и не будет ничего страшного).
Что бы быть подобием java-ссылки, наш SmartPointer должен уметь:
-выполнять операцию присваивания таких же SmartPointer-ов или SmartPointer-ов совместимых типов,
-выполнять операцию присваивания значения NULL,
-выполнять операцию сравнения.
-знать, сколько его копий существует на данный момент.
При всем этом не должно происходить утечек памяти. То есть если один указатель перезаписывается другим, то память на которую указывал "затертый" указатель, должна быть освобождена.
Объявление этого SmartPointer-a я изобразил так:
template<class T>
class AkSmartPointer{
private:
int *ptrCount;
T* ptrObject;
//Удаление 1 экземпляра ССЫЛКИ:
void removeRef();
public:
//Конструкторы:
//"По умолчанию":
AkSmartPointer();
//При создании экземпляра объекта:
AkSmartPointer(T *object);
//Копирование:
AkSmartPointer(const AkSmartPointer<T> &sp);
//Оператор присваивания:
AkSmartPointer<T> & operator=(const AkSmartPointer<T> &s;sp);
//Оператор проверки на равенство:
bool operator == (const AkSmartPointer<T> &sp) const;
//Удаление 1 экземпляра ССЫЛКИ:
~AkSmartPointer();
//Поучение ссылки на объект:
T * operator ->();
};
Вроде ничего не забыл :) Получение указателя необходимо для доступа к интерфейсу указуемого объекта. Реализацию такого SmartPointer-а я сделал вот такой:

class AkSmartPointerException{
public:
AkSmartPointerException( const char *msg, const char *fname, int line){
printf("Error: %s at %s:%i\n", msg, fname, line);
}
};
template<class T>
class AkSmartPointer{
private:
int *ptrCount;
T* ptrObject;
public:
//Конструкторы:
//"По умолчанию" просто обнуляем все указатели:
AkSmartPointer(){
ptrCount = NULL;
ptrObject = NULL;
}
//При создании экземпляра объекта:
AkSmartPointer(T *object){
ptrObject = object;
if(ptrObject != NULL){
ptrCount = new int(1);
printf("New reference 0x%X created.\n", (unsigned)ptrObject);
}else
ptrCount = NULL;
}
//Копирование:
AkSmartPointer(const AkSmartPointer<T> &sp){
ptrCount = sp.ptrCount;
ptrObject = sp.ptrObject;
if(ptrCount != NULL){
(*ptrCount)++;
printf("New copy of reference 0x%X created, %i references total\n",
(unsigned)ptrObject, *ptrCount);
}
}
//Оператор присваивания:
AkSmartPointer<T> & operator=(const AkSmartPointer<T> &sp){
//Если данный объект уже равен присваемому:
if(sp.ptrCount == ptrCount && sp.ptrObject == ptrObject)
return *this;// ничего не делаем, просто возвращаем текущее значение.
//если выполняется только одно условие из двух - то происходит нечто странное,
//и мы выкидываем исключение:
else if(sp.ptrCount == ptrCount || sp.ptrObject == ptrObject)
throw AkSmartPointerException("ptrCount or ptrObject is equal in different smart pointers", __FILE__, __LINE__);
removeRef();//удаляем у текущего объекта ссылку
ptrCount = sp.ptrCount;
if(ptrCount != NULL)
(*ptrCount)++;
ptrObject = sp.ptrObject;
if(ptrObject != NULL && ptrCount != NULL)
printf("New copy of reference 0x%X created, %i references total\n",
(unsigned)ptrObject, *ptrCount);
return *this;
}
//Оператор проверки на равенство:
bool operator == (const AkSmartPointer<T> &sp) const{ return ptrObject == sp.ptrObject; }
//Удаление 1 экземпляра ССЫЛКИ:
~AkSmartPointer(){removeRef(); }
//Получение ссылки на объект:
T * operator ->(){
if(ptrCount != NULL)
return ptrObject;
throw AkSmartPointerException("Null pointer dereferencing", __FILE__, __LINE__);
}
//Удаление 1 экземпляра ССЫЛКИ:
private:
void removeRef(){
if(ptrCount != NULL){
(*ptrCount)--; //Если ссылок не осталось - зануляем все остальное:
if(*ptrCount <= 0){
unsigned x = (unsigned)ptrObject;
delete ptrCount;
if(ptrObject != NULL)
delete ptrObject;
printf("All references 0x%X deleted.\n", x);
}else
printf("One reference 0x%X removed, %i remaining\n",
(unsigned)ptrObject, *ptrCount);
ptrCount = NULL;
ptrObject = NULL;
}
else if(ptrObject != NULL)//если ptrCount == NULL, но ptrObject != NULL
throw AkSmartPointerException("ptrCount == NULL, but ptrObject != NULL", __FILE__, __LINE__);
else
printf("Do nothing - ptrCount and ptrObject is null.\n");
}
};
Наверное, все понятно и так, но немножко комментариев напишу. Каждое действие по созданию или удалению ссылок на объект я сопровождаю выводом на экран, дабы было видно, какие функции-члены SmartPointer-а вызываются при манипуляциях с ним, отсюда куча printf().

В операторе присваивания сначала проверяем, не присваиваем ли мы нашей ссылке ее же значение. Если да - число ссылок не изменилось и ничего делать не надо. Если нет (не выполняются оба условия sp.ptrCount == ptrCount и sp.ptrObject == ptrObject, либо лишь одно из них), то проверяем: оба условия не выполняются или только одно из них. Если только одно условие не выполняется-например, sp.ptrCount == ptrCount, но sp.ptrObject != ptrObject, - это значит, что ссылки на разные объекты имеют общий счетчик, а это недопустимо. Таким образом, в случае выполнения только одного условия из двух, сообщаем об ошибке. Если оба условия не выполняются, то все ОК - одна ссылка перезаписывается другой. В результате присваивания одна из ссылок должна быть удалена (та, что стояла в левой части выражения присваивания) и появится еще одна новая ссылка (та, что стояла в правой части этого выражения). Для удаления ссылки вызываем функцию removeRef у объекта-владельца этой ссылки.

Все остальное по-моему достаточно очевидно (если честно, упарился описывать :) ). Эту реализацию SmartPointer я разместил в файле refCount.hpp.

Уфф, добрались до самого интересного (говорят, русским программистам всегда не терпится запустить программу и увидеть ее в работе. Это правда :) ). Написал я тест и запустил, скрестив пальцы под столом. Сначала хотел его целиком тут привести, но потом передумал. Тест просто показал, что такой SmartPointer вроде бы работает именно так, как я ожидал. Приведу здесь только ту часть теста, которая показала, что происходит при создании объектов и как работает vector.
#include <vector>
#include "refCount.hpp"
struct TestObject{
double x, y;
TestObject(double _x, double _y): x(_x), y(_y){}
};
using namespace std;
typedef AkSmartPointer<TestObject> TestObjectSmartPtr;
int main(int n, char **args){
printf("Declare vector<TestObjectSmartPtr>:\n");
vector<TestObjectSmartPtr> refvect;
{ //Начало блока.
printf("Declare objects a, b, c:\n");
AkSmartPointer<TestObject> sp_a, sp_b, sp_c;
printf("Create object a:\n");
sp_a = new TestObject(1.0, 1.0);//всего 1 ссылка на a
.....
} //Конец блока.
Этот участок кода дал мне такую вот распечатку:
Declare vector:
Declare objects a, b, c:
Create object a:
New reference 0x3F3C90 created.
Do nothing - ptrCount and ptrObject is null.
New copy of reference 0x3F3C90 created, 2 references total
One reference 0x3F3C90 removed, 1 remaining
Хорошо видно, какую последовательность вызовов порождает выражение

sp_a = new TestObject(1.0, 1.0);//всего 1 ссылка на a
Расписывать как все происходит, не буду, дабы не утомлять читателей. :)

Теперь - о том,что касается работы vector. Свой тест я откомпилил в MinGW версии 3.4.2, в Borland C++ builder 5 и в MSVC 6.0 c подключенным компилятором IA-32 v5.0. Что касается создания объектов, и в том и в другом случае распечатки были почти идентичны (за исключением значений адресов объектов). Но что касается vector - разница заметна. В моем тесте есть такой кусок:

printf("Addition object a:\n");
refvect.push_back(sp_a);//2 ссылки
printf("Addition object b:\n");
refvect.push_back(sp_b);//2 ссылки
printf("Addition object c:\n");
refvect.push_back(sp_c);//2 ссылки
Откомпилированный под MinGW, этот кусок вывел на экран следующее:
Addition object a:
New copy of reference 0x3F3C90 created, 2 references total
Addition object b:
New copy of reference 0x3F3C90 created, 3 references total
New copy of reference 0x3F23B8 created, 2 references total
One reference 0x3F3C90 removed, 2 remaining
Addition object c:
New copy of reference 0x3F3C90 created, 3 references total
New copy of reference 0x3F23B8 created, 3 references total
New copy of reference 0x3F23E8 created, 2 references total
One reference 0x3F3C90 removed, 2 remaining
One reference 0x3F23B8 removed, 2 remaining

То есть каждый раз при добавлении 1 элемента в конец вектора, под весь вектор выделялся новый участок памяти, туда копировались элементы из "старого" вектора, а старый участок памяти удалялся(!). То же самое в версии STL MSVC 6.0 (но это ладно, она очень древняя). Похоже, в Borland-е все сделано куда разумнее. Откомпилированный в Borland-е тот же кусок программы вывел на экран гораздо меньше сообщений:
Addition object a:
New copy of reference 0x65415C created, 2 references total
Addition object b:
New copy of reference 0x654180 created, 2 references total
Addition object c:
New copy of reference 0x6541A4 created, 2 references total

Как видно, реализация vector от Borland все же лучше аналогичной в MinGW. Похоже, у Borlan-да при создании вектора сразу резервируется блок под несколько элементов.
Ну и под конец - один из способов, как приведенную реализацию SmartPointer-а заставить работать неправильно:
AkSmartPointer a = new Object(...);
Object *b = a.operator->();
delete b;//пипец!

Но имхо, это будет уже маразм ради маразма :) Если знаете, как с таким SmartPointer-ом организовать утечку памяти - плиз, кидайте в комменты примерчик, но только без описанного маразма или ему подобных (желательно программу целиком). Циклические ссылки приветствуются, но желательно, что бы Ваш пример был компилируемым и запускаемым :). К моему стыду, я до сих пор не знаю, как циклические ссылки можно организовать в C++ (мне один раз это было надо, но я их реализовал так криво, что тошно вспоминать).
P.S. Мне сложно было реализовать ситуацию, когда 2 объекта разных типов должны были ссылаться друг на друга.
5

Воспоминания о лете...

Минувшим летом мы с мужем отдыхали на море. 10 дней в Лазаревском: море, горы, солнце и отсутвие компьютера... Вот это отдых для жены программиста, ну, и разумеется, для самого программиста:)

Сейчас, когда на улице всего +5° С, а батареи греют лишь в одной комнате из трех, воспоминания о летней поездке согревают, если не ноги, то хотя бы душу:))

1

Мой словарный запас

Воть, тест прошел. На проверку словарного запаса :)
Итак, ваш запас: Ваш словарный запас просто великолепен, вероятно вы профессионально владеете языком и решаете кроссворды с легкостью!
Пройти тест
1

Можно ли сделать C++ безопаснее?

Конечно, я наверное, таким вопросом слишком на многое замахнулся. Пусть не "безопасным", но хотя бы чуть менее "опасным" в том, что касается работы с динамической памятью. Я считаю C++ "опасным" языком в этом отношении и думаю, что многие со мной согласятся. С какими основными проблемами встречается человек, пишущий на C++? Думаю, не сильно ошибусь, если скажу, что это утечки памяти и выход за границы диапазонов динамических массивов. С первой проблемой дело обстоит очень сложно, зато незначительные утечки памяти редко вызывают крах программы, если она не работает круглосуточно (например, некая игра для PC, текстовый процессор или какой-нибудь Computer-Aided Design). А вот выход за границы порой валит прогу в самом интересном месте. Даже если вы используете только safe arrays со всяческими проверками и генерацией исключений - все равно бывает, что не один час уйдет на поиск места, где произошла ошибка.
Признатся, я перешел с C++ на Java во многом именно из-за проблем с памятью. Но, кроме сборщика мусора, мне в Java импонирует еще одна вещь: в случае возникновения исключения во время исполнения программы, Java распечатывает стек вызовов! Устраняя глюки (те, что вызывают исключения) в программах на Java, я даже отладчик не использовал, и долгое время (первый год) не пользовался им вообще. После C++ это было что-то!!! Сообщения об ошибках в C++ которые выплевывались при генерации исключений и распечатка содержимого стека (одни цифири) не давали непосвященному вроде меня почти никакой информации о том, где произошла ошибка. Особенно, если использовались шаблоны, а ошибка происходила где-нибудь в debug/vector. В этой статейке я расскажу о своих попытках распечатки стека вызовов в C++ (в случае генерации исключения), в таком же виде, в каком я привык это видеть в Java. Специально для этой этого я написал маленькую программку (надеюсь, моя статья не такая паршивая, как эта программа :)))).

#include <stdio.h>
#include <vector>
void fillDoubleVector(vector<double> &vect){
vect.resize(20);
unsigned size = vect.size();
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*(double)size - size/(double)(i+1);
}
//-----------------------------------------------------
void fillLongVector(vector<long> &vect){
vector<double> dvect;
fillDoubleVector(dvect);
unsigned size = dvect.size();
vect.resize(size);
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*size - size/(i+1);
}
//-----------------------------------------------------
int main(int n, const char **args){
vector<long> lvect;
fillLongVector(lvect);
for(int i=0; i < lvect.size(); i++)
printf("lvect[%i] = %i\n", i, lvect[i]);
return 0;
}

Программу сохранил в файле test.cpp откомпилил в MinGW:

g++ -o test.exe -D _GLIBCXX_DEBUG -g3 test.cpp

Определил _GLIBCXX_DEBUG , что бы в программе использовалась отладочная версия STL (которая хотя бы сообщает о таких ошибках, как выход за пределы диапазона). Получил вот такое сообщение об ошибке:

C:/DEV-CPP/BIN/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/
debug/vector:192:
error: attempt to subscript container with out-of-bounds index 20,
but container only holds 20 elements.

Objects involved in the operation:
sequence "this" @ 0x0074FDC0 {
type = N15__gnu_debug_def6vectorIdSaIdEEE;
}
abnormal program termination
Замечательное и очень информативное сообщение об ошибке! Только местоположение ошибки указано весьма туманно. Было бы еще змечательно, что бы после этого еще и стек вызовов распечатывался. Переделал я эту программулину, вставив в начало и конец каждой функции блоки try-catch:

#include <stdio.h>
#include <exception>
using namespace std;
#include <vector>
//Класс-исключение:
class MyException : public exception
{
public:
MyException(const char *msg, int line)
{
printf("Error in file %s : %i\n", msg, line);
}
};
//--------------------------------------------------
void fillDoubleVector(vector<double> &vect){
try{
vect.resize(20);
unsigned size = vect.size();
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*(double)size - size/(double)(i+1);
}catch(exception ex){
throw MyException(__FILE__, __LINE__);
}
}
//-----------------------------------------------------
void fillLongVector(vector<long> &vect){
try{
vector<double> dvect;
fillDoubleVector(dvect);
unsigned size = dvect.size();
vect.resize(size);
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*size - size/(i+1);
}catch(exception ex){
throw MyException( __FILE__, __LINE__);
}
}
int main(int n, const char **args){
try{
vector<long> lvect;
fillLongVector(lvect);
for(int i=0; i < lvect.size(); i++)
printf("lvect[%i] = %i\n", i, lvect.at(i));
}catch(exception ex){
printf("Error in file %s : %i\n", __FILE__, __LINE__);
}
return 0;
}

Откомпилил, запустил - млин, результат нисколько не изменился!!! Чертовщина, блин! Похоже, что в vector при выходе за пределы массива не выбрасывается исключение, во всяком случае в реализации MinGW. Полез в заголовочные файлы (исходников всей библиотеки, к сожалению, не было) - так и есть, не выбрасывается даже в отладочной версии.
Потом уже прочел в описании STL, что оператор индексирования [] исключение не выбрасывает и проверок никаких не делает, но зато есть функция-член at(), которая работает точно так же как оператор [], но проверки делает и исключение выбрасывает.
Отладочную версию файла vector я несколько раскурочил (перекрестившись и сохранив его копию), вставив в тело функции operator [] вызов функции at() и добился-таки желаемого:

Error in file test2.cpp : 23
Error in file test2.cpp : 36
Error in file test2.cpp : 47

Жаль, что старое информативное сообщение об ошибке уже не выводится. Но все можно исправить, для этого надо немного поковырять исходники библиотек(насколько я понимаю - вставить вместо вызова функции abort() генерацию исключения). Найти бы тока время, что бы этим заняться, и трафик, что бы их (stdlibc++ sources) скачать :)
Еще в дополнение замечу, что кроме выходов за границы есть и другие ошибки, которые можно было бы так отлавливать - главное, что бы при их возникновении выкидывалось исключение. Конечно, ручками вставлять кучу блоков try/catch вряд ли кто захочет, да и вряд ли это нужно делать ручками. Но вот если написать такую приблуду, которая будет сама это в исх. код вставлять, потом все это компилить, а потом из исходного кода удалять... Напишите для меня такую, кто-нибудь, заплатить не обещаю, но обещаю 100 раз скачать :)
0

Эмуляция действий пользователя в Java

Пишу заготовку для графического редактора на Java. Захотелось мне, что бы в моей проге было как в P-CAD: при изменении масштаба указатель мыши всегда указывал на одну и ту же точку изображения. В P-CAD для пущей крутизны эта точка еще и переносится в центр окна. Сначала все сделал без перемещения указателя к центру. Вроде ничего, приятно :) Потом захотелось крутизны. Но вот незадача - понятия не имею, как в Java указатель мышки программно переместить. Пробовал генерировать события для окна через dispatchEvent() - ни хрена не помогает. После получаса поисков с грехом пополам на форуме Винграда нашел подсказку - оказывается, для этих целей аж специальный класс есть!!! Под названием java.awt.Robot.
Перемещение указателя мышки в нужную точку компонента оказалось очень простым:

import java.awt.*;
...
try{
Robot rob = new Robot();
Point2D p1 = comp.getLocationOnScreen();
rob.mouseMove((int)p1.getX() + comp.getWidth()/2,
(int)p1.getY() + comp.getHeight()/2);
}
catch(AWTException ex){
System.out.println("Can't move mouse to center, error in DrawingWindow.java:195");
}

Здесь у меня comp - экземпляр класса, наследованного от класса java.awt.Panel.
0

Техномаразматический сон

Да простит меня автор блога Уголок техномаразма за заимствование меткого термина. :) Просто не получается никак иначе назвать ахинею, приснившуюся мне сегодня ночью.
Сюжет:
Едем мы с моей женой куда-то на междугородном автобусе. Все замечательно, в окошко глядим. Потом после остановки мы почему-то оказываемся сидящими у двери (но не у той, что впереди, а той, которая у некоторых автобусов есть в середине). Чтой-то я стал этой дверью баловаться, и она стала на ходу открываться, причем как-то странно - навстречу потоку воздуха (т.е. при движении автобуса поток воздуха стремится распахнуть дверь, а не препятствует этому -так на старых легковых автомобилях делали передние дверцы). Я ее закрою - она опять открывается... Водила терпел, потом остановил автобус, и что-то с этой дверью сделал, и мы опять поехали. Тут я гляжу - дверь стала надуваться(!) Ну, я говорю супруге - смотри, мол, он режим надувания двери включил, что бы она в дверном проеме застряла и не открывалась(!) (Счас пишу эти строки и охреневаю, как же мне такое только в голову пришло). Потом эта дверь опять стала сдуваться. Я говорю жене - ну, похоже проблемы, прокол в двери, придется ремонтировать (!)
Следующая картина - забираем с женой из салона катушку ниток и иголку. Потом почему-то оказываемся в каком-то садовом домике рядом со столом, на котором разложено куча инструментов и лежит сдувшаяся дверь(!) Водитель стоит напротив меня за столом и говорит - мол, дыра в двери, надо зашивать, требуется нитка и иголка (!). Предлагает свою иголку - она какая-то гигантская, сантиметров 10 длиной и загнутым кончиком. Мне она не нравится, я ищу ту иголку, что мы подобрали в салоне, иголку нахожу, а нитку - нет, где-то эта нитка под кучей инструментов завалялась.
Занавес.
Обычно сны у меня сразу забываются, а этот почему-то сохранился в памяти. Блин, и ведь накануне не напивался - только персики ел... и арбуз немного...
3

Шаблоны в Java

В Java 5 наконец-то появились шаблоны. После C++ без них как-то совсем тяжко :) Эмулировать шаблоны, создавая все структуры данных (т.е. классы) для базового типа object - имхо, идея не очень хорошая.

В-общем, я обрадовался, все хорошо и замечательно, в душе пели птички, в реальности тоже (и при этом прическу и одежду не портили), полная идиллия.
До тех пор, пока мне не приспичило написать (подобно тому, как раньше писал в C++) :

class foo<T>
{
...
T data[] = new T[n];
...
}

Тут меня компилятор долбанул темечком об грешную землю.
Полез в книжки, почитал - и правда, низзя в Java 5 и 6 создавать внутри шаблонного класса объекты типа-параметра. Честно говоря, жалко было, но как-то без создания объектов обошелся, и ничего. Не так давно подумал, что это ограничение все же можно обойти.

Например, можно написать на Java код для шаблона динамического массива. Правда, в конструктор массива придется передать все же ссылку на один инициализированный объект нужного или производного от него класса.
Реализация вот такая:

package examples;
import java.lang.*;
import java.lang.reflect.*;

public class ExampleGen<T1>
{
T1 dim[] = null;
Class cl = null;
//Конструктор. передаем в кач-ве параметра инициализированную ссылку на объект
//нужного (или производного от него) типа
public ExampleGen(T1 var)
{
cl = var.getClass();
}
public void setSize(int n)
{
T1 tmp[] = (T1 [])Array.newInstance(cl, n);
if(dim != null)
for(int i=0; i < dim.length && i < n; i++)
tmp[i] = dim[i];
dim = tmp;
}
public int getSize()
{
return dim.length;
}
public T1 get(int n)
{
return dim[n];
}
public void set(int n, T1 value)
{
dim[n] = value;
}
public T1 [] getArray()
{
T1 tmp[] = (T1 [])Array.newInstance(cl, dim.length);
if(dim != null)
for(int i=0; i < dim.length; i++)
tmp[i] = dim[i];
return tmp;
}
public static void main(String [] args)
{
ExampleGen data;
data = new ExampleGen<T1>( new Double(1.0) );
data.setSize(20);
for(int i=0; i < data.getSize(); i++)
data.set(i, new Double(Math.cos((double)i+1)) );
System.out.println("Values of structure:");
for(int i=0; i < data.getSize(); i++)
System.out.println("Value #" + i + " = " + data.get(i));

Double a[] = data.getArray();
System.out.println("\nValues of array:");
for(int i=0; i < data.getSize(); i++)
System.out.println("Value #" + i + " = " + a[i]);
}
}

Все работает. Инициализированная ссылка нужна лишь для того, что бы получить описание класса - заполнить поле cl.
0

Java-процессор на FPGA

Недели 2 назад пришла идея, что можно было бы такую штуку сделать. Прогуглил - оказалось такая идея людям приходила еще в 1999 г, когда я о Java только краем уха слышал. Казалось бы, перспективная штука, но большинство найденных мной документов по данной теме датированы 2004 г. или раньше. Единственное более позднее сообщение, которое я смог найти - A methodology for Java/FPGA co-design Это только автореферат... Хотелось бы почитать саму работу, тока пока не понятно, как с авторами связаться. Судя по тому, что в автореферате написано, в создании "java-процессора" имеются существенные успехи.
0

GUI front-end для GDB

Наверняка все, кто используют компиляторы из пакета MinGW слышали про такую штуку, как GDB (GNU Debugger). Весчь полезная, но на мой взгляд - шибко навороченная и неудобная. Привык при отладке видеть исходный текст, причем лучше - всех файлов проекта, а не только первого. Поиски нормальной оболочки для отладчика окончились ничем. Более-менее неплохая оболочка для GDB была в составе Gygwin-a, но... Скачивать весь Cygwin только из-за одной этой оболочки - мутило, на диалапе-то. Вот поэтому я и начал в 2006 году писать графическую оболочку для GDB под названием GDBShell. Выполняются простейшие операции: установка/удаление точек останова, выполнение программы до позиции курсора, пошаговое выполнение, пошаговое выполнение с заходом в блоки, можно посмотреть/изменить значение переменных.
Страничка, посвященная GDB.
Последнюю версию (которую я разместил только вчера), с исправленными глюками, можно скачать тут:
Download GDBShell: GDB GUI front-end
Скриншоты прилагаются :)))))

Главное окно и меню отладки:

Всплывающая менюшка (по-моему, должно быть довольно удобно)

Окно просмотра/изменения значений переменных:
0

Л.И. Брежнев о мировом финансовом кризисе

4

Перенаправление ввода/вывода в Java

Воть и сам муж появился... :))) Пока попробую опубликовать те статейки, котрые писал на vingrad.ru
21.01.08
9.28
Да-а-а-а... "совсем ерунда - перенаправить вывод программ куда-нибудь...". Все оказалось не так просто, как я думал. В тех книжках, где я имел счастье видеть примеры запуска внешних программ либо не было сказано, как это делать вообще, либо описано это очень неточно и неполно. К такому выводу я пришел, когда у меня наконец-то все заработало.

Итак, сперва я решил просто запустить внешнюю программу, но не notepad, как в большинстве примеров в книгах по java (на мой взгляд, более бесполезного примера трудно придумать), а внешнюю java-программу.Строка запуска должна быть вот такой:
java -cp . -ea HelloWorld
где программка HelloWorld выводит всем известную со времен Кернигана и Ричи фразу :). Сам текст внешней программки:
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
System.out.println("Hello, world!");
System.exit(2);
}
}
Дело видете ли в том, что мне помимо запуска внешней программы надо получить код, возвращаемый программой, и если он отличается от 0 (т.е. произошла ошибка), вывести сообщение, которое вывела внешняя программа. В данном случае возвращаю 2, что бы было понятно что получившееся значение - результат работы именно этой программы, а не что-либо еще (например, код ошибки, возвращаемый java-интерпретатором). Вот мое первое творение (весьма далекое от того, что должно быть, поскольку в кач-ве образца использовался простейший пример с notepad-ом из книги "Полный справочник по Java" Г.Шилдта):
import java.lang.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello", "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}

Запускаем RunHello и пока все отлично - программа работает, только сообщение "Hello, world!" не выводится.
Теперь немножко изменим программку HelloWorld:
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
for (int i = 1; i <= 40; i++)
System.out.println("Hello, world! " + i);
System.exit(2);
}
}

Теперь сообщение должно вывестись 40 раз подряд, вместе с порядковым номером вывода. Запускаем RunHello и.. ничего не происходит - программа благополучно повисает. Дабы этого не происходило, нужно читать вывод программы. То есть нужно читать из потоков, возвращаемых методами getInputStream() и getErrorStream(), определенных в классе Process.
Модифицируем нашу программку RunHello:
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream()));
int m;
do{
m = stdinput.read();
System.out.print((char)m);
}while(m >= 0);
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
Вывод дочернего процесса естественно, не обязательно печатать. Все замечательно работает, и нет проблем. Но пока рано радоваться :) Теперь немножко модифицируем программу HelloWorld. Помимо стандартного устройства вывода будем печатать еще и на стандартное устройство ошибок.
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
for (int i = 1; i <= 40; i++)
{
System.out.println("Hello, world! " + i);
if(i < 30)
System.err.println("It's prints to stderr" + i);
}
System.exit(2);
}
}
Теперь наша программа выведет некоторое число сообщений "Hello, world! " и встанет, поскольку мы в програме RunHello не читаем из стандартного устройства ошибок. Что ж, надо читать :)
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream())),
stderror = new BufferedReader(new InputStreamReader(p.getErrorStream()));
int m1, m2;
do{
m1 = stdinput.read();
if(m1 > 0)
System.out.print((char)m1);
m2 = stderror.read();
}while((m1 >= 0) || (m2 >= 0));
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
Пока у меня на Win98 все отлично работает. Попробуем в программе HelloWorld количество циклов увеличить с 40 до 80:
for (int i = 1; i <= 80; i++)
{
System.out.println("Hello, world! " + i);
if(i < 30)
System.err.println("It's prints to stderr" + i);
}
Как видим, приключения еще не кончились! :) Опять программа подвисает, у меня - после вывода 40-го сообщения. Если честно, пока я проходил все эти стадии прозрения, мой стол немного пострадал, поскольку я лупил по нему кулаком от злости :) Так же немного пострадал мой кулак :).
Но все же есть выход из положения, позволяющий запускать любые внешние программы, что угодно печатающие на консоль и в стандартное устройство ошибок. Прежде чем читать что-то из потоков stderror или stdinput, необходимо проверить, можно ли производить чтение и не приведет ли это к блокировке процессов. Это можно сделать, вызывая для потоков метод ready(). Он возвращает true(истину), если читать из потока можно. Итак, окончательная версия программки RunHello у меня получилась такой:
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Projects\\Java\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream())),
stderror = new BufferedReader(new InputStreamReader(p.getErrorStream()));
int res=0;
int m1;
do
{
if( stderror.ready() || stdinput.ready())
{
if(stdinput.ready())
{
m1 = stdinput.read();
if(m1 >= 0)
System.out.print( (char)m1 );
}
if(stderror.ready())
stderror.read();
}
else
try{
res = p.exitValue();
break;
}catch(java.lang.IllegalThreadStateException ex){}
}while(true);
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}

В цикле сначала проверяем, готов ли какой-либо поток к чтению из него. Если оба потока не готовы - есть вероятность, что программа закончила свою работу (но это еще не факт!! Как корректно проверить, завершил процесс работу или нет, я так и не нашел в документации на Java API. Если кто знает - напишите, буду весьма признателен! :) ) После пробуем получить значение, возвращаемое внешней программой. Если внешняя программа завершила работу - завершаем цикл и печатаем возвращаемый код.
0

Записка № РАЗ

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