пятница, 1 января 2010 г.
Планы
Для работы:
· В первую очередь (в первых месяцах), хотелось бы занятся изучением процесса разработки ПО.
· Администрирование Windows Server
· Нужно более хорошее знание Oracle, pl/sql.
· Основы информационной безопасности.
Для себя
· Основы Linux
· Основы Adobe Flash (просто интересно)
В конце года можно будет подвести итоги :D
четверг, 1 октября 2009 г.
Функция без явного определения
typedef int (*pf)(int, int);
char c[] = {85,-119,-27,-117,69,12,3,69,8,93,-61,-112};
pf sum = (pf)c; //reinterpret_cast отказывается, поэтому так.
cout << sum(2,3); //Вывод 5.
* This source code was highlighted with Source Code Highlighter.Теперь sum - функция сложения, являющаяся аналогом этой:int sum(int a, int b){return a+b;}Сначала я написал нормальную функцию sum, затем байт за байтом просмотрел ее содержимое в памяти. К сожалению sizeof к функциям неприменим, поэтому я, после sum, определил еще одну функцию, и предположил, что в памяти они тоже рядом. Их адреса различались на 12 байт.
UPD: Одной строкой:
cout << ((int (*)(int, int))"\x55\x89\xE5\x8B\x45\x0C\x03\x45\x08\x5D\xC3\x90")(2,3);
Функции
Повторюсь, что я веду этот блог не как учебное пособие, а пытаюсь объяснить самому себе сложные моменты (написание топика заставляет достаточно подробно вникнуть в суть), а так же необычные вещи удивившие меня. Еще я люблю очень маленькие(особенно в одну строчку :), но показательные примеры.
Вопрос которым я задался: можно ли вызвать функцию, имея только ее адрес хранящийся в long. Разумеется просто ради интереса, без практической пользы. Без reinterpret_cast обойтись явно не смогу. Еще понадобится тип функции.
char f(){return 'f';}
typedef char (*PF)(); //именно такой тип у f()
long a = reinterpret_cast<long>(f); //получаем число...
cout << (reinterpret_cast<PF>(a))(); //... говорим, что это число - указатель на функцию. Скобки () приводят вызову функции.
суббота, 26 сентября 2009 г.
Константы
const int a = 1;
int b;
cout << &a << &b; //У меня, при запусках, b всегда имеет адрес меньше(на 4 байта), чем a.
int* p = (&b+1); //Это адрес константы a
cout << *p //1
*p = 2; //Меняем
cout << a << endl << *p; //1 2
Вроде поменяли, однако a осталось прежним, но по ее адресу лежит число 2. Дело в том, что компилятор просто заменяет все вхождения a на 1, и обычно для этого не выделяет память. Но когда мы написали &a, компилятор все-таки выделил память для нее и записал туда значение. Но все-равно перед компиляцией заменил в коде a на 1. Память вроде бы пропадает впустую, ее даже можно изменить.
понедельник, 21 сентября 2009 г.
reinterpret_cast
long a = 2829155;
cout << reinterpret_cast< char* >(&a);Выведется строка «c++». Не отдам руку на отсечение, что у всех она будет именно такой, это требует дополнительного исследования.В первой строке записали четырехбайтовое число. Затем взяли адрес переменной и нагло наврали компилятору, что этот адрес совсем не указатель на long. Это указатель на char. Как компилятор интерпретирует указатель на char? Как строку.
Почему именно «с++»? 2829155 == 0x002B2B63. То есть число 2829155 хранится в виде последовательности байт: 0x63(==99), 0x2B(==43), 0x2B(==43), 0x0(==0). Если посмотреть таблицу символов, то убедимся, что 99 это символ «c», а 43 – «+». Байт с нулем интерпретируется как конец строки.
Совсем нетрудно написать программу обратного преобразования, из строки в число
Я не смогу толково объяснить все тонкости reinterpret_cast, для этого есть учебники. Однако, точно могу сказать, что он запросто превращает указатель одного типа в указатель другого. А это влечет совершенно другую интерпретацию байтов памяти, в чем можно убедиться выше. Об опасностях нетрудно догадаться. Если в примере взять число побольше, то 4-й байт уже не будет равен нулю. Значит, приведенная к char* строка будет продолжаться до первого нуля, но уже неизвестно где он будет.
С классами тоже интересно.
class A {
public:
int i;
};
void main(){
int b = 1;
cout << (*reinterpret_cast< A* >(&b)).i; /*1*/
}Тут говорится, что по адресу &b вовсе не число, а переменная класса A. Нужно ли говорить насколько это все опасно.
пятница, 18 сентября 2009 г.
Массивы
var z : array[1..3] of Integer;
то переменная z будет массивом и все тут. Словно обертка над более низкоуровневыми типами. Хотя, может быть, это не совсем верно. Массив в C++:
int z[3];
В памяти отводится место(непрерывное) для трех int, а переменная z - указатель на первый элемент. Квадратные скобки просто делают удобнее работу с указателями. А раз так, то для получения значения, можно писать не только z[i], а еще и *(z+i).
z[i] == *(z+i)
&z[i] == z+i
Иными словами: Берем адрес первого элемента массива (адрес это z). Следующий элемент находится на sizeof(int) байт (у меня 4) дальше. Но количество байт нам знать не нужно, так как компилятор в курсе, что z - указатель на int, поэтому z+1 указывает на sizeof(int) дальше чем z, т.е на следующий элемент массива (на z[1]). Аналогично и z+i.
Еще пример
int* p = z+1;
int* p = &z[1];
//Обе записи эквивалентны.
cout << p[2]; //p - не массив, просто квадратные скобки смещают указатель и сразу разыменовывают.
В этом примере p[1]==z[2].
четверг, 17 сентября 2009 г.
cout << (long)&"Hello" << (long)&"World"; //4464640 4464646 (опять эти числа, почему всегда они)
Любопытно, если записать одинаковые слова:
cout << (long)&"Hello" << (long)&"Hello"; //4464640 4464640
то можно увидеть одинаковые адреса. Видимо компилятор произвел оптимизацию.
понедельник, 14 сентября 2009 г.
char* и строковые литералы
воскресенье, 13 сентября 2009 г.
Указатели
int i = 7;
int* pi = &i; //Указатель на int. Проинициализирован адресом i.
int** ppi = &pi //Указатель на int*, т.е. указатель на указатель на int. Проинициализирован адресом pi.
Когда-то я не понимал, что физически, указатель - это переменная хранящая адрес. Вот и все. Взглянем на структуру памяти после выполнения вышеприведенного кода.
Прямоугольники символизируют ячейки памяти. Правее указаны их адреса. Разумеется адреса могут быть другими, при следующем запуске. Стрелки добавлены исключильно для наглядности.
Переменная i может хранить любое значение типа int. pi - адрес любой переменной int. ppi - адрес любой переменной типа int*(например адрес pi).
Все это легко проверяется. В моем примере:
cout << i << (long)&i; //7 2293572
cout << pi << (long)π //2293572 2293568
cout << ppi << (long)&ppi; //22968 2293564
cout << *pi << **ppi << (long)*ppi; //7 7 2293572
(long) - преобразование перед выводом. Без него адреса выводятся в шеснадцатеричном виде. * - операция разыменования. То есть *pi возвращает значение переменной, находящуюся по адресу pi. *ppi == pi == &i
Был еще сложный для меня момент: указатели на int и на int* (и вообще все указатели) так похожи, раз хранят только адрес. Почему же нельзя сделать просто указатель на переменную любого типа. Вообще говоря можно. Но в таком случае можно легко ошибиться. Например случайно указать на переменную int, и думать про себя, что это объект. А благодаря типизации, компилятор будет следить за этим, а среда разработки будет помогать автозавершением кода для более сложных типов.
Уф, пока писал и проверял, сам все понял :)
Перечислимые типы
С этими типами посложнее, чем в Delphi. Самый простой пример:
enum colors{RED, GREEN, BLUE};
RED, GREEN, BLUE - константы, равные числам 0, 1, 2 соответственно. Их значения можно задавать самостоятельно:
enum colors{RED=0, GREEN=1, BLUE=9}; //Должны быть целыми
Использование достаточно очевидно:
colors x = GREEN; //z == 1
colors z = 9; //Ошибка компиляции.
colors z = colors(9); //А вот так все хорошо. z == BLUE
if (z = BLUE) {/* */};
Любопытно (по крайней мере для меня), что переменной z можно присвоить другое значение кроме этих трех:
colors z = 2; //Как мы уже выяснили - это ошибка компиляции
colors z = colors(2); // Так можно. z == 2
Интересно, в каких пределах можно задавать значения? У меня не сошлось со Страуструпом.
Он говорит, что выделяется непрерывный диапазон, в зависимости от минимального и максимального значения. Диапазон определяется минимальным количеством бит, требуемым для представления перечисления. Пример оттуда:
enum e2{a=3, b=9} //диапазон 0:15
Поэтому, по его словам, e2 f2 = e2(20) не определено.
Границы диапазонов связаны со степенями двойки, но я не буду переписывать точное правило. Размер sizeof не может превышать sizeof(int).
Не знаю почему, но как я не крутил, sizeof всегда был равен 4. По всей вероятности sizeof(enum) = sizeof(int).
Вывод, который я сделал: Перечислимый тип - это фактически тот же int, но с личными именами некоторых чисел