2023 C/C++ ソフトウェア開発エンジニア スクール採用面接 よくある質問の知識ポイントの復習パート 8

目次

52. ベクトルはどのように保存されますか?

  • 基になるベクトルの配列に格納されているstringオブジェクトのアドレス
  • ランダムアクセスを保証する必要があるが、文字列自体はそのサイズをすぐに決定できないため、文字列オブジェクトを直接保存することはできませんが、アドレスは保存されます。

53. epoll の基本原理

53.1 選択とポーリングの比較

epoll の仕組みは非常に効率的です

  1. select と poll は線形手法を使用してソケット コレクションを処理する必要があるかどうかを検出しますが、epoll は赤黒ツリーに基づいて検出されるコレクションを管理します。
  2. select と Paul は毎回コレクション全体を線形にスキャンする必要がありますが、epoll はコールバック メソッドであり、どのコレクションが応答する必要があるかを直接知ることができます。
  3. select と poll は、どのファイル記述子の準備ができているかを知るために、返されたセットを判断する必要があります。epoll は、準備ができたファイル記述子セットを直接取得できます。
  4. epoll には最大ファイル記述子の制限はありません。システムが開くことができるファイル記述子の制限によってのみ制限されます。

3つの操作機能

  1. 作成
  2. 管理の追加と維持
  3. 準備ができたファイル記述子があるかどうかを確認する
#include <sys/epoll.h>
// 创建epoll实例,通过一棵红黑树管理待检测集合
int epoll_create(int size);
// 管理红黑树上的文件描述符(添加、修改、删除)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 检测epoll树中是否有就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

ためにint epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 联合体, 多个变量共用同一块内存        
typedef union epoll_data {
    
    
 	void        *ptr;
	int          fd;	// 通常情况下使用这个成员, 和epoll_ctl的第三个参数相同即可
	uint32_t     u32;
	uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    
    
	uint32_t     events;      /* Epoll events */
	epoll_data_t data;        /* User data variable */
};
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// epfd  就是创建的epfd实体
// op    用于指定执行什么管理操作,是删除、添加、还是修改
// fd    用于指定要添加的socket是啥
// event 是一个结构体,用来指定fd相关事件以及一些用户数据,事件的话有EPOLLIN:读事件、EPOLLOUT:写事件、EPOLLERR:异常事件

ためにint epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

// epfd    是epoll对象实例
// events  是返回的就绪状态的文件描述符
// maxevents 表示 结构体的容量
// 0-> 不等待  -1-> 一直等待  
53.2 ET および LT の動作モード

LT は水平トリガーのことです。これはデフォルトの動作方法です。ファイル記述子に応答する必要がある場合、対応するイベントがトリガーされます。処理しない場合、または完全に処理されない場合は、引き続きイベントが発生します。引き金

ET はエッジ トリガーを指します。これは比較的高レベルの方法で、ファイル記述子に応答する必要がある場合、処理するかどうかに関係なく、対応するイベントが 1 回だけトリガーされます。

  • データをループで受信し、すべてを一度に処理することを期待します
  • したがって、データの送受信、特に受け入れにはノンブロッキング関数を使用する必要があります。

54. プロセス、スレッド、コルーチンとその通信方法についての理解

プロセス、スレッド、コルーチン、およびそれらの通信方法についての理解

54.1 プロセスの意味
  • プロセスは、オペレーティング システムによるリソース割り当ての基本単位であり、システム リソースを適用したり所有したりできますが、これは動的な概念です。
  • プロセスには独自のアドレス空間、つまり独自の独立したメモリ空間があり、異なるプロセスには IPC、つまりプロセス間通信 (パイプライン、シグナル、メッセージ キュー、共有メモリ、セマフォ、ソケット) が必要です。
  • 各プロセスには独自のシステム リソースがあるため、コンテキスト切り替え (スタック、レジスタ、仮想メモリ、ファイル ハンドルなど)のオーバーヘッドは比較的大きく、プロセス切り替えはオペレーティング システムによって完了します。
54.2 スレッドの意味
  • スレッドはプロセスの実行エンティティであり、CPU スケジューリングの基本単位です。スレッド自体は基本的にシステム リソースを所有せず、動作に必要な一部のリソース (PC、レジスタ、スタック) のみを所有します。
  • 同じプロセスに属するスレッドは、そのプロセスが所有するすべてのリソースを共有します。スレッド間の通信は主に共有メモリ、グローバル変数などに依存します。
  • プロセスのスレッド切り替えに比べて、 PC、レジスタ、スタックの保存と復元だけで済むためオーバーヘッドが少なく、スレッド切り替えはOS側で完了します。
54.3 コルーチンの意味
  • コルーチンはユーザー モード軽量スレッドであり、コルーチンのスケジューリングはカーネル モードに入ることなくユーザーによって完全に制御されます。
  • コルーチンはリソースが少なく、独自のレジスタとスタックを持っています。コルーチンの切り替えはこれらのリソースを保護するだけで済みます。コルーチンのコンテキスト切り替えのオーバーヘッドは最小かつ最速です。
  • コルーチンはロックせずにグローバル変数にアクセスできますコルーチンのスケジューリングはユーザーによって制御されるため、読み取り/書き込みの競合がなく、ロックを解除できます-私自身の理解です)
54.4 プロセス間通信 IPC

https://www.cnblogs.com/zhuminghui/p/15405591.html

  1. プロセス間の通信はカーネルを経由します

  2. 一般的に使用される IPC メソッドは、パイプライン、メッセージ キュー、共有メモリ、セマフォ、シグナル、ソケットなどです。

  • なぜコミュニケーションが必要なのでしょうか?

    • 異なるプロセス間のアドレス空間は独立しているため、連携が必要な場合は特別な通信方法、つまりIPC 通信が必要になります。

54.4.1、パイプライン (パイプ)

匿名パイプは半二重で単方向です。相互に通信する必要がある場合は 2 つのパイプを作成する必要があり、これらのパイプはアフィニティによって関連付けられたプロセス間でのみ使用できます。アフィニティは親子プロセスのようなものです。

既知のパイプも半二重で一方向ですが、無関係なプロセス間の通信が可能です。

缺点:パイプライン通信方式は非効率的であり、プロセス間の頻繁なデータ交換には適していません。

54.4.2、メッセージキュー

メッセージ キューはカーネルに格納されるメッセージ リンク リストであるため、メッセージ キューのライフ サイクルはカーネルとオペレーティング システムに従います。

メッセージ本文はユーザー定義のデータ型です。メッセージの送信者と受信者はメッセージ本文のデータ型に同意する必要があります。したがって、各メッセージ本文は、固定フォーマットとサイズのストレージ ブロックです。

  • 2 つのプロセス間の通信は電子メールの送信に似ており、あなたが電子メールを送信し、私が返信します。

  • 优点:これにより、信号送信情報が少なく、パイプラインはフォーマットされていないバイト ストリームでのみ使用でき、バッファ サイズが制限されているという欠点が解決されます。

  • 缺点:通信がタイムリーではなく、メッセージ本文のサイズが制限されており、ユーザー状態とカーネル状態の間のコピー プロセスが頻繁に発生します。

54.4.3、共有メモリ

共有メモリは両方のプロセスがアクセスできるメモリの一部であるため、一方のプロセスがデータを書き込むと、もう一方のプロセスはそのデータをすぐに参照します。したがって、効率は非常に高く、すべての IPC の中で最も高速です

54.4.4、信号量

セマフォは主に、共有メモリと連携して共有リソースの相互排他と同期を確保し、データ アクセスの競合や上書きの問題を防ぐために使用されます。

54.4.5. 信号

Linuxさまざまな意味を表す数十の信号が内部で定義されています。シグナルはいつでもプロセスに送信して、イベントが発生したことをプロセスに伝えることができます。

  • 信号源は主に硬件来源软件来源

  • プロセスがシグナルを受信したとき、一般的に 3 つの処理方法があります。

    1. Linux が提供するデフォルトのアクションを信号ごとに実行します。
    2. 信号をキャプチャし、独自に定義した処理関数を実行します
    3. 無視

54.4.6、ソケット

異なるデバイス上の異なるプロセス間の通信に使用できます。

54.5 スレッド間の通信

スレッド通信の主な目的はスレッドの同期であるため、プロセスのようにデータを交換するための通信メカニズムはありません。

主にミューテックス、読み書きロック、条件変数、セマフォなどのロック機構

55.defineマクロ定義の使い方

マクロ定義のdefineの使い方

  1. ヘッダーファイルの二重インクルードを防止する
    #ifndef HEAD_H
    #define HEAD_H
    // 头文件内容
    
    #endif
    
  2. 2 つの数値の最大値と最小値を求める

    本質は、1 つの文、1 行のコード、および 1 つの式を使用してマクロの機能を実現することであり、式の結果がマクロのターゲットになります。

    #define MAX(x, y) ((x) > (y) ? (x) : (y))
    #define MIN(x, y) ((x) > (y) ? (y) : (x))
    // 宏函数的调用
    cout << MAX(10, 100) << endl; // 100
    
  3. 配列要素の数を返します
    #define ARR_SIZE(arr) (sizeof((arr)) / sizeof((arr[0])))
    
    // 调用
    int arr[15] = {
          
          0};
    cout << ARR_SIZE(arr) << endl; // 15
    
  4. 変数のアドレスを取得する
    #define GET_PTR(var) ((void *)&(var))
    
    // 调用
    double d = 100.11;
    cout << GET_PTR(d) << endl; // 0x72fdc8
    
  5. 指定されたアドレスからバイトまたは int を取得します
    #define MEM_B(ptr) (*((char *)(ptr)))  // 获取一个字节
    #define MEM_INT(ptr) (*((int *)(ptr))) // 从一块地址上去一个int
    
  6. 文字を大文字に変換する
    #define UPCASE(c) (((c) <= ('z') && (c) >= ('a')) ? ((char)((c) - ('a') + ('A'))) : (c))
    
    cout << UPCASE('a') << endl; // A
    cout << UPCASE('A') << endl; // A
    

56. 配列、ポインタ、配列名の詳細

  1. 配列名自体は配列属性を持ち、配列名に対して配列サイズを直接計算できますsizeof。この属性は代入後は存在しません。
    // 数组名本身有数组属性
    int arr[15] = {
          
          0};
    cout << size(arr) << endl; // 60
    
    // 如果将数组名传递到函数中就不带有数组属性了,将退化为一个指针
    cout << func1(arr) << endl;  // 如果调用下面的函数,就会丧失数组属性
    
    int func1(int arrp[])
    {
          
          
        return sizeof(arrp);  // 退化为指针后,sizeof得到的仅仅是指针大小
    }
    
  2. 32 ビット マシン ポインターのサイズは 4 バイト、64 ビット マシン ポインターのサイズは 8 バイトです。
  3. 配列名の使用法は one に似ています指针常量。つまり、ポインタのポイントは変更できず、配列の最初のアドレスを指します。
    int arr[10] = {
          
          0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    arr++;  // 不合法---->因为数组名arr是一个指针常量,自身是不可以改变的
    int a =  *(arr + 1);    // 等价于arr[1];
    
    int* ptr = arr; 		//通过赋值操作,用一个普通的指针指向了数组arr的首地址
    ptr++;	// 合法
    ++ptr;	// 合法
    
    int* ptr2 = &arr[0];  	// 等价于 int* ptr = arr; 结果都是使得一个普通指针指向了数组的首地址
    
  4. 配列の最初のアドレスは、配列の最初の要素の開始アドレスと同じです。
    int arr[3] = {
          
          1, 2, 3};
    // arr指向数组的首地址
    // &arr[0]指向数组第一个元素的起始地址
    // 两者是同一个位置
    
  5. ポインターの移動と操作
    • ポインタとポインタは減算演算(要素が何個離れているか)のみを実行でき、乗算、除算、加算などの演算は実行できません。

    • ポインタは定数を追加することしかできません

      int arr[10] = {
              
              0};
      int * p = arr;
      *(p + 9) 指向最后一个元素 等价于 arr[9]
      同样也等价于*(arr + 9)
          
      在编译器中,arr[i]的访问过程就是转化为 *(arr + i)然后访问的
      
  6. ポインタの配列について数组指针
    // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
    int *arr[10];
    // 声明一个数组指针,该指针指向一个 int 类型的一维数组
    int (*arr)[10];  
    

57. コンパイルの具体的なプロセス

コンパイルの具体的なプロセス

  1. コンパイルプロセスは 4 つのステップに分かれます预处理———编译汇编链接
  2. 前処理
    • ヘッダー ファイルの展開、マクロ定義の展開、コメントの削除
    • gccコマンドgcc -E ...
    • 生成された中間ファイルはソースコード.i文件のままです
  3. コンパイル
    • .iファイルをファイルにコンパイルします.s。つまり、ソース コード ファイルをアセンブリ コードに変換します。
    • gccコマンドgcc -S ...
    • 生成される中間ファイルは.s文件アセンブリコードです
    • この段階で、主なコンパイルの最適化が行われます。
  4. 編集
    • .sファイルを 1 行ずつファイルに変換します.o。つまり、アセンブリからバイナリ マシン コードに変換します。
    • gccコマンドgcc -c ...
    • 生成された中間ファイルは、.o文件
  5. リンク
    • この段階で、GCC はリンカーを呼び出して、プログラムが呼び出す必要があるライブラリと複数のファイルを.oリンクして、実行可能バイナリ ファイルを生成します。
    • gccコマンド:gcc + 源文件直接コンパイルします。他のパラメーターは必要ありません。
    • デフォルトで生成されますa.out文件。生成されたファイルの名前をカスタマイズするために使用することもできます-o 文件名(このコマンドは上記の段階でも使用できます)

おすすめ

転載: blog.csdn.net/qq_40459977/article/details/127519327