ここでの著者のコンパイラ標準は C++17 です。
トピック 1
以下のプログラムを実行した結果
int main() {
int x = 19;
x &= 0x1f;
printf("%d %d\n", x << 1, x >> 1);
return 0;
}
19 バイナリ0001_0011
0x1f バイナリ0001_1111
したがってx = 0001_0011
、 1 ビットを左にシフトする0010_0110
と、 1 ビットを右にシフトすると、0000_1001
したがって、答えは 38,1 です
トピック 2
64 ビット システムでは、次の構造体定義があり、次のプログラムの出力は次のとおりです。
#include <stdio.h>
typedef unsigned int GEN_OBJ_T[7];
typedef enum {
COLOR_SYS_A,
COLOR_SYS_B,
COLOR_SYS_C,
COLOR_SYS_D,
COLOR_SYS_E,
COLOR_SYS_F,
COLOR_SYS_G,
COLOR_SYS_H,
COLOR_SYS_AUTO = 30,
COLOR_SYS_UNKOWN,
} COLOR_SYS_T;
#pragma pack(4)
typedef struct {
union {
unsigned char u_char;
COLOR_SYS_T u_sys_t;
unsigned long int u_long_int: 24;
signed long long _long: 16;
int u_int[96 / COLOR_SYS_UNKOWN];
} u;
} GEN_SUB_OBJ_T;
#pragma pack()
typedef struct _my_obj_t {
signed char a;
GEN_OBJ_T obj;
GEN_SUB_OBJ_T sub_obj;
struct _my_obj_t *next;
struct _my_obj_t *prev;
} MY_OBJ_T;
int main(void) {
printf("$d %d\n", sizeof(MY_OBJ_T), sizeof(GEN_SUB_OBJ_T));
}
この質問の目的は、2 つの構造の長さを見つけることです。最初にMY_OBJ_T
、いくつかの知識ポイントを含むこの構造を見てください。
- デフォルトでは列挙は 0 から始まりますが、途中 (COLOR_SYS_AUTO=30) で切り捨てられるため、COLOR_SYS_UNKOWN の値は 31 になります。そのため、COLOR_SYS_UNKOWN の値は 31 になります。
- 96 / ここでは配列に入れているのでCOLOR_SYS_UNKOWNの値は3になっており、整数が必要ですが、C言語では整数は四捨五入ではなく直接四捨五入されます。
#pragma pack(4)
この機能は、整列されるバイト数を指定することです。ここでは、4 バイト整列が指定されています。これはunsigned char
1 バイトですが、整列後は 4 バイトになります。列挙型は整数で、これも 4 バイトです。cunsigned long int
では言語標準では 4 バイトにすることもできます 8 バイトにすることもできます 筆者が試してみましたが、GNU コンパイラでは 4 バイトsigned long long
または 8 バイトになります。int u_int[96 / COLOR_SYS_UNKOWN]
の長さは配列の長さで、12 バイトであり、アライメント数のちょうど 3 倍です。#pragma pack()
設定されたバイト アライメント番号はキャンセルされます。つまり、以下のプログラムはコンパイル時に強制的に 4 バイトに従ってアライメントする必要がありません。- したがって、この構造の長さは 12 で、これはキメラの中で最長です
int u_int[96 / COLOR_SYS_UNKOWN]
。 #pragma pack()
これを 8、つまり 8 バイトのアライメントに設定すると、答えは 16 になります。これは、共用体には 8 バイトのアライメントが必要で、配列の長さは 12 バイトで、自動的に 2 つの 8 バイトで埋められるためです。これは 16 バイトです。
それではMY_OBJ_T
この構造を見てください
struct _my_obj_t *next
このポインタは、構造体のアライメント長が 8 バイトであることを決定します。signed char
1 バイト、GEN_OBJ_T
4*7 バイトなので、2 つで 4 8 バイトになります。GEN_SUB_OBJ_T
この構造体は 12 バイトで、アライメント後は 2 8 バイトになります- つまり全体は (4+2+1+1) 8 バイトになります
概要: この質問には、構造体、共用体、列挙型の配置に関する知識が非常に豊富です。非常に参考になります。
トピック 3
次の 2 次元配列で宣言されたエラーは次のとおりです。
char a[2][3] = {
0};
char a[][3] = {
0};
char a[2][] = {
0};
char a[][] = {
0};
答えは CD です。なぜ b が正しいのか不思議です。実際、配列を宣言するときに行数を指定する必要はなく、次のような初期化リストに基づいて自動的に推測されます。
char b[][3] = {1, 2, 3, 4, 5, 6};
彼は、それが 2 行 3 列の 2 次元配列であると自動的に推測できます。
つまり、多次元配列を作成する場合、最初の次元のサイズは無視できますが、2 番目の次元は無視できません。
最初の次元を無視する場合は、コンパイラが配列のサイズを自動的に推測できるようにするために、パラメータ リストも提供する必要があります。実際には、最初の次元を失ったわけではなく、コンパイラがこの作業を完了するのに役立ちました。
トピック 4
次の記述のうち間違っているものはどれですか
int main(){
int *p;
*p = 10;
return 0;
}
// A 该代码可以编译通过
// B 该代码执行会有segmentation fault
// C 该代码可以正常执行结束
// D 该代码无法通过编译
答えは CD です。このコードは一見問題ないように見えますが、実際には問題があります。ポインタは定義されていますが、ポインタに値が割り当てられていません。つまり、ポインタはどこを指しているのかわかりません。値の書き込みポインタが指す場所に直接アクセスすると、セグメントがエラーになります。セグメントフォルトは、アドレスプログラムが操作アドレスに適用されなかったか、アクセスできないアドレス空間にアクセスしたためです。セグメントエラーが発生した場合に正常に終了するにはどうすればよいですか。ただし、このコードはコンパイルできます。
トピック5
次のコードの出力:
class Base {
public:
Base() {
Init();
}
virtual void Init() {
printf("Base Init\n");
}
void func() {
printf("Base func\n");
}
};
class Derived : public Base {
public:
virtual void Init() {
printf("Derived Init\n");
}
void func() {
printf("Derived func\n");
}
};
int main() {
Derived d;
((Base *) &d)->func();
return 0;
}
結果は次のとおりです
Base Init
Base func
その理由は、サブクラスの初期化では最初に親クラスを初期化する必要があるため、Base の Init 関数が直接呼び出され、その後Drived Init
サブクラスのコンストラクターに実行Init
関数がないため出力されないためです。
サブクラスを親クラスに強制転送した後は、サブクラスの代わりに親クラスの func 関数が実行され、サブクラスに戻して初めてサブクラスが実行可能となります。
ここではさらに難しい質問が 5 つあり、残りは少し簡単なので、これ以上入力しません。。。