2020 Meituan Autumn Recruitment C ++ Selected Interview Questions and Answers (Part 2)

  1. Algorithme de filtre Bloom Le filtre
    Bloom utilise un vecteur binaire combiné avec une fonction de hachage pour enregistrer si des données existent déjà dans l'ensemble.
    Le processus d'exécution du filtre Bloom est le suivant:
    appliquez d'abord pour un ensemble de bits contenant des bits SIZE, et définissez tous les bits sur 0.
    Ensuite, utilisez plusieurs (k) fonctions de hachage différentes pour hacher les données cibles et obtenir k valeurs de hachage (assurez-vous que la valeur de hachage ne dépasse pas la taille de SIZE), puis utilisez la valeur de hachage dans le jeu de bits comme indice La valeur de bit à est mise à 1. Puisque k fonctions de hachage sont utilisées, les informations pour enregistrer un élément de données définiront les valeurs de k bits à 1 dans l'ensemble de bits.
    En raison de la stabilité de la fonction de hachage, les k positions de bits correspondant à deux éléments quelconques des mêmes données dans l'ensemble de bits sont exactement les mêmes. Ensuite, lors de la détection si une donnée a été enregistrée dans l'ensemble de bits, il est seulement nécessaire de vérifier si les k valeurs de hachage de la donnée à la position correspondante dans l'ensemble de bits sont toutes marquées à 1, au contraire, tant qu'elles existent Si la position de bit correspondant à une valeur de hachage n'est pas marquée comme 1, cela prouve que la valeur n'a pas été enregistrée.
    Exemple d'utilisation Un exemple de
    filtre Bloom est le suivant:

Insérez la description de l'image ici

Le processus général est le suivant:
initialisez d'abord un tableau binaire, la longueur est définie sur L (8 sur la figure) et la valeur initiale est entièrement égale à 0.
Lors de l'écriture d'une donnée avec A1 = 1000, il doit effectuer des calculs de fonction de hachage H (ici, 2 fois); similaire à HashMap, le HashCode et L calculés sont modulés puis situés à 0 et 2, à cet endroit La valeur de est définie sur 1.
A2 = 2000 équivaut à régler 4 et 7 positions sur 1.
Lorsqu'il y a un B1 = 1000 qui doit être jugé s'il existe, il effectue également deux opérations de hachage et se situe à 0 et 2. À ce stade, leurs valeurs sont toutes 1, donc on considère que B1 = 1000 existe dans l'ensemble.
Quand il y a un B2 = 3000, il en va de même. Lorsque le premier hachage est situé à index = 4, la valeur du tableau est 1, donc la deuxième opération de hachage est effectuée et le résultat est situé à index = 5, la valeur est 0, donc on considère que B2 = 3000 n'existe pas dans l'ensemble. Avantages
et inconvénients
Avantages La
complexité temporelle est O (n) et le filtre Bloom n'a pas besoin de stocker l'élément lui-même, utilise un tableau de bits et occupe un petit espace.
Inconvénients
Grâce au filtrage de floraison, nous pouvons juger avec précision qu'un nombre n'existe pas dans un certain ensemble, mais pour la conclusion qu'il existe dans l'ensemble, le filtrage de floraison aura des faux positifs (il peut y avoir deux ensembles de données différentes mais plusieurs valeurs de hachage Exactement la même situation). Cependant, en contrôlant la taille de l'ensemble de bits (c'est-à-dire SIZE) et le nombre de fonctions de hachage, la probabilité de conflit peut être contrôlée dans une très petite plage, ou le problème de conflit de hachage peut être complètement résolu en créant une liste blanche supplémentaire.
La formule pour calculer le taux d'erreur de jugement est (1-e (-nk / SIZE)) k, où n est le nombre de données cibles, SIZE est la taille de l'ensemble de bits et k est le nombre de fonctions de hachage utilisées; en supposant qu'il y en a 10 millions Pour les données à traiter, la taille de l'ensemble de bits est de 2 ^ 30 (environ 1 milliard, ce qui occupe 128 Mo de mémoire). En utilisant 9 fonctions de hachage différentes, les valeurs de hachage calculées de deux éléments de données sont les mêmes 9 fois. (Quel que soit l'ordre), la probabilité est de 2,6e-10, soit environ un sur 3,8 milliards.
Insérez la description de l'image ici
Insérez la description de l'image ici
Plus de questions d'entrevue et de matériel vidéo des sociétés Internet de première ligne, vx faites attention à la Zero Voice Academy pour recevoir gratuitement!
Insérez la description de l'image ici

2. Le processus d'un fichier source C ++ du texte au fichier exécutable

?
Pour les fichiers source C ++, quatre processus sont généralement requis du texte au fichier exécutable:
Étape de prétraitement: analyse et remplacement des relations d'inclusion de fichiers (fichiers d'en-tête) et des instructions précompilées (définitions de macro) dans les fichiers de code source pour générer des fichiers précompilés .
Étape de compilation: convertissez les fichiers précompilés après le prétraitement en code d'assemblage spécifique et générez des fichiers d'assemblage.
Étape d'assemblage: convertissez les fichiers d'assemblage générés lors de la phase de compilation en code machine et générez des fichiers objets déplaçables.
Étape de liaison: fichiers objets multiples Et les bibliothèques requises sont liées dans le fichier objet exécutable final

3. Quelle API liée à la mémoire partagée?

Linux permet à différents processus d'accéder à la même mémoire logique et fournit un ensemble d'API. Le fichier d'en-tête se trouve dans sys / shm.h.
1) Créer une nouvelle mémoire partagée shmget
int shmget (key_t key, size_t size, int shmflg);
key: valeur de clé de mémoire partagée, qui peut être comprise comme une marque unique de mémoire partagée.
size: taille de la mémoire partagée
shmflag: l'identification des droits de lecture et d'écriture du processus de création et des autres processus.
Valeur de retour: identifiant de mémoire partagée correspondant, retourne -1 en cas d'échec
2) Connectez la mémoire partagée à l'espace d'adressage du processus actuel shmat
void * shmat (int shm_id, const void * shm_addr, int shmflg);
shm_id: identifiant de mémoire partagée
shm_addr: Spécifiez l'adresse à laquelle la mémoire partagée est connectée au processus en cours, généralement 0, ce qui signifie que le système choisit.
shmflg: Bit de drapeau
Valeur de retour: Pointeur vers le premier octet de la mémoire partagée, retourne -1 en cas d'échec
3) Le processus actuel sépare la mémoire partagée shmdt
int shmdt (const void * shmaddr);
4) Contrôle la mémoire partagée shmctl
et le sémaphore semctl La fonction est similaire, contrôlant la mémoire partagée
int shmctl (int shm_id, int command, struct shmid_ds * buf);
shm_id: shared memory identifier
command: il y a trois valeurs
IPC_STAT: obtenir l'état de la mémoire partagée, copier la structure shmid_ds de la mémoire partagée dans buf .
IPC_SET: Définit l'état de la mémoire partagée, copiez buf dans la structure shmid_ds de la mémoire partagée.
IPC_RMID: supprimer la mémoire partagée
buf: structure de gestion de la mémoire partagée.

4. Quelle est la composition du modèle de réacteur.

Le modèle de réacteur exige que le thread principal soit uniquement chargé de surveiller si un événement se produit sur la description du fichier, et si tel est le cas, il en informera immédiatement le thread de travail. De plus, le thread principal ne fait aucun autre travail de fond, lit et écrit des données et accepte de nouvelles informations. La connexion et le traitement des demandes des clients sont terminés dans le thread de travail. Le modèle se compose comme suit:
Insérez la description de l'image ici

1) Handle: Le handle du système d'exploitation, qui est une abstraction des ressources au niveau du système d'exploitation. Il peut s'agir d'un fichier ouvert, d'une connexion (Socket), d'une minuterie, etc. Puisque le mode Reactor est généralement utilisé dans la programmation réseau, il fait généralement référence à Socket Handle, qui est une connexion réseau.
2) Démultiplexeur d'événement synchrone (multiplexeur d'événement synchrone): blocage et attente de l'arrivée d'une série d'événements dans le handle. Si le blocage et l'attente du retour, cela signifie que le type d'événement retourné peut être exécuté sans blocage dans le handle retourné. Ce module est généralement implémenté en utilisant la sélection du système d'exploitation.
3) Dispatcher d'initiation: Utilisé pour gérer le gestionnaire d'événements, le conteneur de EventHandler, utilisé pour enregistrer, supprimer EventHandler, etc. en outre, il sert également d'entrée du mode Reactor pour appeler la méthode de sélection du démultiplexeur d'événements synchrones pour bloquer l'attente du retour de l'événement et l'attente bloquée. Lorsqu'il retourne, il sera distribué au gestionnaire d'événements correspondant selon le handle de l'événement, c'est-à-dire que la méthode handle_event () dans le EventHandler sera rappelée.
4) Gestionnaire d'événements: définissez la méthode de traitement des événements: handle_event () pour le rappel d'InitiationDispatcher.
5) Gestionnaire d'événements concrets: interface EventHandler, qui implémente une logique de traitement d'événements spécifique.
5. Quel contexte le thread doit-il enregistrer? Quelles sont les fonctions des registres SP, PC, EAX? Le
thread doit enregistrer l'ID de thread actuel, l'état du thread, la pile, l'état du registre et d'autres informations pendant le processus de commutation. Les registres comprennent principalement le SP PC EAX et d'autres registres, et leurs principales fonctions sont les suivantes:
SP: pointeur de pile, pointant vers l'adresse supérieure de la pile actuelle
PC: Compteur de programme, qui stocke la prochaine instruction à exécuter
EAX: Registre d'accumulation, le registre par défaut pour l'addition et la multiplication

5. Il y a 10 millions de messages courts, avec des répétitions, enregistrés sous forme de fichiers texte, une ligne par ligne. Veuillez prendre 5 minutes pour trouver les 10 principaux éléments récurrents.

#include<iostream>
#include<map>
#include<iterator>
#include<stdio.h>
using namespace std;
 
#define HASH __gnu_cxx
#include<ext/hash_map>
#define uint32_t unsigned int
#define uint64_t unsigned long int
struct StrHash
{
    
    
 uint64_t operator()(const std::string& str) const
 {
    
    
 uint32_t b = 378551;
 uint32_t a = 63689;
  uint64_t hash = 0;
 
 for(size_t i = 0; i < str.size(); i++)
 {
    
    
 hash = hash * a + str[i];
 a = a * b;
 }
 
 return hash;
 }
 uint64_t operator()(const std::string& str, uint32_t field) const
 {
    
    
 uint32_t b = 378551;
 uint32_t a = 63689;
 uint64_t hash = 0;
 for(size_t i = 0; i < str.size(); i++)
 {
    
    
 hash = hash * a + str[i];
 a  = a * b;
 }
 hash = (hash<<8)+field;
 return hash;
 }
};
struct NameNum{
    
    
 string name;
 int num;
 NameNum():num(0),name(""){
    
    }
};
int main()
{
    
    
 HASH::hash_map< string, int, StrHash > names;
 HASH::hash_map< string, int, StrHash >::iterator it;
 NameNum namenum[10];
 string l = "";
 while(getline(cin, l))
 {
    
    
 it = names.find(l);
 if(it != names.end())
 {
    
    
 names[l] ++;
 }
 else
 {
    
    
 names[l] = 1;
 names[l] = 1;
 }
 }
 int i = 0;
 int max = 1;
 int min = 1;
 int minpos = 0;
 for(it = names.begin(); it != names.end(); ++ it)
 {
    
    
 if(i < 10)
 {
    
    
 namenum[i].name = it->first;
 namenum[i].num = it->second;
  if(it->second > max)
 max = it->second;
 else if(it->second < min)
 {
    
    
 min = it->second;
 minpos = i;
  }
 }
 else
 {
    
    
 if(it->second > min)
 {
    
    
 namenum[minpos].name = it->first;
 namenum[minpos].num = it->second;
 int k = 1;
 min = namenum[0].num;
 minpos = 0;
 while(k < 10)
  {
    
    
 if(namenum[k].num < min)
 {
    
    
 min = namenum[k].num;
 minpos = k;
 }
 k ++;
 }
 }
 }
 i++;
 
 }
 i = 0;
 cout << "maxlength (string,num): " << endl;
 while( i < 10)
 {
    
    
 cout << "(" << namenum[i].name.c_str() << "," 
<< namenum[i].num << ")" << endl;
 i++;
 }
 return 0;
}

6. Puis-je demander le principe de malloc, quelles sont les fonctions de l'appel système brk et de l'appel système mmap?

La fonction malloc est utilisée pour allouer dynamiquement de la mémoire. Afin de réduire la surcharge de la fragmentation de la mémoire et des appels système, malloc utilise une méthode de pool de mémoire, en appliquant d'abord un gros bloc de mémoire en tant que zone de tas, puis en divisant le tas en plusieurs blocs de mémoire, en utilisant le bloc comme unité de base de la gestion de la mémoire. Lorsque l'utilisateur demande de la mémoire, un bloc libre approprié est directement alloué à partir de la zone de tas. malloc utilise une structure de liste liée implicite pour diviser le tas en blocs continus de différentes tailles, y compris les blocs alloués et les blocs non alloués; en même temps, malloc utilise une structure de liste liée explicite pour gérer tous les blocs libres, c'est-à-dire utiliser une liste doublement liée pour connecter des blocs libres Ensemble, chaque bloc libre enregistre une adresse continue et non allouée.
Lorsque la mémoire est allouée, malloc parcourt tous les blocs libres à travers la liste liée implicite et sélectionne les blocs qui répondent aux exigences d'allocation; lorsque la mémoire est fusionnée, malloc utilise la méthode de notation des limites et détermine si les blocs avant et après chaque bloc ont été alloués. S'il faut effectuer une fusion de blocs.

Lorsque malloc s'applique à la mémoire, il s'applique généralement via des appels système brk ou mmap. Lorsque la mémoire demandée est inférieure à 128 Ko, la fonction système brk sera utilisée pour allouer dans la zone de tas; lorsque la mémoire demandée est supérieure à 128 Ko, la fonction système mmap sera utilisée pour allouer dans la zone de mappage.

7. Puis-je demander à l'espace d'adressage virtuel Linux

Afin d'empêcher différents processus de concurrencer et de piétiner la mémoire physique tout en s'exécutant dans la mémoire physique en même temps, la mémoire virtuelle est utilisée.
La technologie de mémoire virtuelle fait différents processus dans le processus en cours d'exécution, ce qu'elle voit, c'est qu'ils occupent seuls la mémoire 4G du système actuel. Tous les processus partagent la même mémoire physique et chaque processus mappe et stocke uniquement l'espace de mémoire virtuelle dont il a actuellement besoin avec la mémoire physique. En fait, lorsque chaque processus est créé et chargé, le noyau "crée" simplement la disposition de la mémoire virtuelle pour le processus. Plus précisément, il initialise la liste liée liée à la mémoire dans la table de contrôle de processus. En fait, il ne place pas immédiatement les données du programme correspondant à l'emplacement de la mémoire virtuelle. Et le code (tel que la section .text.data) est copié dans la mémoire physique, juste pour établir le mappage entre la mémoire virtuelle et le fichier disque (appelé mappage de mémoire), attendez que le programme correspondant soit exécuté, l'exception d'erreur de page sera passée Pour copier les données. De plus, pendant l'exécution, il est nécessaire d'allouer dynamiquement de la mémoire. Par exemple, lorsque malloc, seule la mémoire virtuelle est allouée, c'est-à-dire que l'entrée de la table de pages correspondant à cette mémoire virtuelle est définie en conséquence. Lorsque le processus accède effectivement à ces données, le défaut est provoqué. La page est anormale.

Le système de pagination de demande, le système de segmentation de demande et le système de pagination de segment de demande sont tous destinés à la mémoire virtuelle, et le remplacement d'informations entre la mémoire et la mémoire externe est réalisé par demande.
Les avantages de la mémoire virtuelle:
1. Agrandir l'espace d'adressage
2. Protection de la mémoire: chaque processus s'exécute dans son propre espace d'adressage de mémoire virtuelle et ne peut pas interférer les uns avec les autres. Le stockage virtuel fournit également une protection en écriture sur des adresses mémoire spécifiques, ce qui peut empêcher la falsification malveillante du code ou des données.
3. Allocation de mémoire équitable. Après avoir utilisé le stockage virtuel, chaque processus équivaut à avoir la même taille d'espace de stockage virtuel.
4. Lorsque le processus communique, il peut être réalisé par partage de mémoire virtuelle.
5. Lorsque différents processus utilisent le même code, tel que le code du fichier de bibliothèque, une seule copie de ce code peut être stockée dans la mémoire physique. Différents processus n'ont besoin que de mapper leur propre mémoire virtuelle pour économiser la mémoire
6 La mémoire virtuelle est très appropriée pour une utilisation dans les systèmes multi-programmation, et de nombreux fragments de programme sont stockés en mémoire en même temps. Lorsqu'un programme attend qu'une partie de celui-ci soit lue en mémoire, il peut donner la CPU à un autre processus. Plusieurs processus peuvent être conservés dans la mémoire et la simultanéité du système est améliorée.
7. Lorsque le programme doit allouer un espace mémoire continu, il n'a besoin que d'allouer un espace continu dans l'espace mémoire virtuelle et n'a pas besoin de l'espace continu de la mémoire physique réelle. La fragmentation peut être utilisée

Le coût de la mémoire virtuelle:
1. La gestion de la mémoire virtuelle nécessite la mise en place de nombreuses structures de données qui prennent de la mémoire supplémentaire
2. La conversion d'adresses virtuelles en adresses physiques augmente le temps d'exécution des instructions.
3. La page à permutation d'entrée et de sortie nécessite des E / S sur disque, ce qui prend du temps
4. S'il n'y a qu'une partie des données dans une page, cela gaspillera de la mémoire.

Je suppose que tu aimes

Origine blog.csdn.net/lingshengxueyuan/article/details/108603022
conseillé
Classement