Страницы

Поиск по вопросам

вторник, 30 апреля 2019 г.

Доступ к приватным полям класса предка

Насколько я понимаю, при создании объекта класса через new выделяется область в Heap для хранения всех полей как самого класса, так и нестатичных публичных (и protected) полей всех его предков. А затем поочерёдно вызываются конструкторы всех предков, начиная со старшего, которые инициализируют, а возможно перезаписывают значения полей данного объекта. И в конце конструктор самого объекта-наследника инициализирует свои новые поля или, возможно, перезаписывает значения полей, которые уже инициализировали конструкторы предков. Вопрос в следующем. Если у предка есть приватные поля и публичные геттеры к ним, а наследник не переопределяет эти поля и геттеры, то из объекта наследника можно, вызвав унаследованный геттер, получить значение поля из приватного поля предка. Как происходит в действительности? При создании объекта создаются объекты всех его предков в отдельных областях памяти, со всеми своими полями? Или же, что мне кажется более вероятным, JVM понимает, что при наличии публичных геттеров у предка наследник может получить доступ к его приватным полям и поэтому создаёт в области памяти объекта-наследника приватные поля его предка?


Ответ

Объектный модуль класса, помимо прочей информации, содержит информацию о иерархии наследования, порядке следования полей и их размерах. То есть после загрузки класса у виртуальной машины в метаспейсе всегда есть "карта" объектов этого класса, по которой можно вычислить по какому смещению от начала блока памяти находится то или иное поле. При создании нового объекта выделяется блок достаточного объёма, чтобы хранить заголовок объекта, его поля, а также поля всех его суперклассов. Причём поля располагаются в порядке от корня наследования - сначала поля суперкласса, потом подкласса. Благодаря такому расположению "карта" суперкласса подходит для ориентирования в объекте подкласса. Для наглядности определим примитивную иерархию классов
class A { int x; int y; }
class B extends A { int z; }
Операция B obj = new B() выделит в куче такой блок
----------- -- | Заголовок | | ----------- |_ Класс A | x | | | y | | ----------- -- | z | |- Класс B ----------- --
Если мы теперь приведём тип объекта к базовому, то виртуальная машина будет выполнять операции доступа к полям объекта так, будто никакого хвостика, содержащего поле z, просто не существует.
Благодаря Алексею Шипилёву мы можем увидеть это вживую с помощью инструмента jol (Java Object Layout).
import org.openjdk.jol.info.ClassLayout; import org.openjdk.jol.vm.VM;
public class ShowLayout { public static void main(String[] args) throws Exception { System.out.println(VM.current().details()); System.out.println(ClassLayout.parseClass(B.class).toPrintable()); } }
Компилируем
javac -cp jol-cli-0.9-full.jar ShowLayout.java
Запускаем
java -javaagent:jol-cli-0.9-full.jar ShowLayout
Получаем
# Running 64-bit HotSpot VM. # Using compressed oop with 3-bit shift. # Using compressed klass with 3-bit shift. # Objects are 8 bytes aligned. # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
B object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int A.x N/A 16 4 int A.y N/A 20 4 int B.z N/A Instance size: 24 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Для методов каждого класса у JVM тоже есть "карта", указывающая по какому смещению в памяти находится начало того или иного метода. Есть два способа использования этой "карты" - раннее и позднее связывание (на самом деле больше, но это несущественно в текущем контексте). Обычный вызов метода
obj.getZ()
будет скомпилирован в байткод
aload_1 // Загрузка в стек ссылки на obj invokevirtual #4 // Method B.getZ:()I
Инструкция invokevirtual использует позднее связывание. То есть при вызове метода JVM анализирует контекст вызова (call site), определяет какой именно метод нужен и передаёт управление по требуемому смещению. Благодаря этому и возможен полиморфизм.
Вызов метода суперкласса (а также вызовы конструкторов и приватных методов)
super.getX();
будет скомпилирован в байткод
aload_0 // Ссылка this на объект класса B invokespecial #2 // Method A.getX:()I
Инструкция invokespecial использует ранее связывание. То есть ещё на этапе загрузки класса понятно какой именно метод какого именно класса надо будет вызвать, и JVM "зашивает" смещение этого метода в байткод.
Получив управлением метод getX (с помощью JVM, конечно) отсчитает нужное смещение от начала блока памяти, на который указывает переданная ссылка, до места где должно располагаться поле x в классе A, прочитает его значение, положит на вершину стека и вернёт управление вызывающему коду. Даже не догадываясь, что ссылка this на самом деле указывает на объект большего размера, и есть ли в подклассе метод с таким же именем, как у него.

Как корректно записать/считать в/из файла структуру с полями типа string?

Имеется структура
struct User { string login; string password; };
Стоит задача сделать примитивную авторизация пользователя. Т.е. создается файлик, в него записывается заполненный объект вышеуказанной структуры, а при последующих запусках производится запрос логина+пароля, считываются данные из файлика и сравниваются. Код:
#include #include #include #include #include
using namespace std;
struct User { string login; string password; };
void main () { SetConsoleCP (1251); // установка универсальной кодировки SetConsoleOutputCP (1251);
string path; int realsize=0; User u; vector U; bool exit = false;
do { system("cls"); cout<<"Укажите, на каком диске находится файл с регистрационными данными:
"; getline(cin, path); path += ":\\users.txt"; ifstream fin(path, ios_base::binary | ios_base::in);
if (fin.is_open()) { cout<<"Отлично, ваш файл найден!
"; fin.read((char*)&u, sizeof(User)); U.push_back(u); fin.close(); cout << "Введите логин:
"; getline(cin, u.login); cout << "Введите пароль:
"; getline(cin, u.password); if (!U.at(0).login.compare(u.login) && !U.at(0).password.compare(u.password)) { cout << "Вы авторизованы!
"; } else { cout << "Вы не авторизованы!
"; } exit = true; } else { cout << "Файл не найден и будет создан"; ofstream fout (path, ios_base::binary | ios_base::out); if (fout.is_open()) { cout << "Введите логин:
"; getline(cin, u.login); cout << "Введите пароль:
"; getline(cin, u.password); fout.write((char*)&u, sizeof(User)); fout.close(); cout << "Файл создан и данные внесены!
"; } else { cout << "Ошибка при создании файла! Работа приложения будет завершена.
"; exit = true; } } } while(!exit);
system("pause");
}
Вся беда в том, что при выполнении данного кода появляется ошибка:
Необработанное исключение по адресу 0x0FDECCC8 (msvcp110.dll) в test.exe: 0xC0000005: нарушение прав доступа при чтении по адресу 0x0067ADE4.
Кадры стека вызовов:

Путем экспериментов установлено, что замена использования вектора на простой динамический массив никак не влияет на ошибку (т.е. она по-прежнему появляется), а вот замена использования string на char * приводит к устранению проявления данной ошибки. Погуглив, я пришел в выводу, что возникает какая-то ошибка в деструкторе string (могу ошибаться). Может кто-нибудь прояснить ситуацию и дать рекомендации по корректному использованию типа string в данного рода задачах?


Ответ

Самый простой способ (как уже упомянул в комментарии @pavel) - использовать текстовый режим работы с файлом и операторы форматированного ввода/вывода (operator<<, operator>>) для чтения/записи std::string из/в потока.
Чтение:
ifstream fin(path); if (fin) { fin >> u.login >> u.password; }
Запись:
ofstream fout(path); if (fout) { // Разделители нужны для последующего считывания fout << u.login << " " << u.password << "
"; }
При этом данный подход накладывает некоторые ограничения на строки: как минимум они не должны содержать в себе символы пробельной группы, т.к. такой символ будет расценен как разделитель.
Функции istream::read, ostream::write в этом случае не используются вовсе. Причина, по которой они не работают как надо указана в ответе @gbg.

Как сделать плавную прокрутку страницы?

Как делается плавная прокрутка страницы на сайте? Мы скроллим страницу, а она прокручивается не рывками, а плавно, как тут. Какие плагины для этого есть?


Ответ

Нашел для этих целей плагин jQuery.scrollSpeed

Стоит ли заменять анонимную реализацию с одним методом лямбдой?

Intllij Idea предлагает заменить анонимную реализацию с одним методом лямбдой. Но по смыслу это не функциональный интерфейс, а просто переопределение одного метода одного класса. Стоит ли заменять?
return new NextImportIdParams() { @Override public String getCollectionName() { return new CollectionName(); } };


Ответ

Все зависит от того под какую версию java пишете + от принятого code style. Если вам никто не связывает руки, то определенно лямбы делают код более компактным и читабельным.

Запуск ssh-agent на debian

На удаленном сервере для развертывания кода с BitBucket используем защишенное соединение иаутентификацию по ssh ключам. Ксамому серверу подключаюсь через putty (с win10 сижу), и каждый раз приходится заново запускать ssh-agent командой eval $(ssh-agent) и добавлять нужные ключи. Посмотрел в запущенные процессы, все ранее запущенные ssh-agent'ы висят в памяти, но если разорвать соединение с сервером и затем снова открыть, ssh-agent снова недоступен. Подскажи в чем может быть проблема?
devloop@s05657c43:~# ssh-agent SSH_AUTH_SOCK=/tmp/ssh-m8EJNB3MFimr/agent.13061; export SSH_AUTH_SOCK; SSH_AGENT_PID=13062; export SSH_AGENT_PID; echo Agent pid 13062;
devloop@s05657c43:~$ ssh-add .ssh/devloop Could not open a connection to your authentication agent.


Ответ

сам факт запуска процесса ssh-agent на отдалённой машине уже намекает на то, что вы, вероятно, используете его не по назначению.
цитата из man ssh-agent
The idea is that the agent is run in the user's local PC, laptop, or terminal. Authentication data need not be stored on any other machine, and authentication passphrases never go over the network.
мой вольный перевод:
идея в том, что ssh-agent запускается на локальном компьютере. чтобы никакие аутентификационные данные не нужно было хранить на другой машине, и никакие пароли не передавались по сети.

судя по этому фрагменту:
$ ssh-add .ssh/devloop
вы хотите с первой отдалённой машины аутентифицироваться на каких-то других отдалённых машинах по ключу, хранящемуся на удалённой машине (в файле ~/.ssh/devloop).
в таком случае вам нет смысла запускать на этой отдалённой машине процесс ssh-agent. для того, чтобы аутентифицироваться на других машинах с помощью этого ключа, можете воспользоваться любым из перечисленных вариантов:
переименуйте файл ~/.ssh/devloop в ~/.ssh/id_rsa. этот вариант не подойдёт, если такой файл уже есть и его содержимое требуется вам для каких-то других целей. указывайте явно этот файл при подключении:
$ ssh -i ~/.ssh/devloop другая-отдалённая-машина добавьте в начало файла ~/.ssh/config строку:
identityfile ~/.ssh/devloop
тогда программа ssh будет пытаться использовать и этот файл при аутентификации.
если этот файл требуется использовать только для аутентификации лишь на нескольких определённых машинах, тогда лучше вместо указанной строки добавить в конец файла ~/.ssh/config секцию:
host машина1 машина2 машина3 identityfile ~/.ssh/devloop

но если я неправильно угадал, и вам на самом деле необходим процесс ssh-agent, то лучше запускать его в качестве «прослойки» (как он собственно, и используется в современных дистрибутивах), т.е. сразу указывать при подключении команду вида ssh-agent оболочка, примерно так:
$ ssh -t отдалённая-машина ssh-agent bash
опция -t здесь необходима для того, чтобы был отведён tty (если не указывать команду, т.е., инициировать запуск оболочки, то tty отводится автоматически).
а ещё, пожалуй, лучше будет использовать терминальный мультиплексор (screen, tmux и т.п.), который, кстати, тоже можно запускать с «прослойкой» в виде ssh-agent, примерно так:
$ ssh -t отдалённая-машина ssh-agent screen

p.s. и, да, вопрос не несёт в себе никакой дистрибутивной специфики, а касается лишь конкретно клиента openssh

Как прослушивать нажатия клавиш клавиатуры вне приложения?

У меня есть задача: прослушивать нажатия клавиш клавиатуры, вне приложения (в браузере, блокноте или просто на рабочем столе). При помощи чего это можно реализовать? Не нашел никакой информации по этому поводу.


Ответ

Можно использовать готовую библиотеку. Например jkeymaster или jnativehook

Выполняется старая версия кода

Установил Visual Studio 2015. Создал проект. написал простую программу из разряда "привет мир". В какой то момент перестает компилироваться программа, то есть меняю реплику на "мир привет", а выдается "привет мир". То есть старый exe файл запускается. Кто подскажет как побороть?


Ответ

Причина запуска старого варианта кода может быть в том, что новый код содержит ошибки и согласно настройкам среды производится запуск последней удачной сборки.
Проверьте настройки на странице Tools - Options - Projects & Solutions - Build & Run