[C言語] Cパズルブック 1.4.5 ビット演算子 論理否定演算子 !

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]

問1.4.5の解法は以下の通りです。

#include <stdio.h>
#define PRINT(int) printf("%d\n",int)

int main() {
    int x , y;
    
    x = 1; y = -1;

    // 問1.4.5
    PRINT( !x | x);   // 1

    // 論理否定演算子 !を優先
    PRINT( (!x) | x); // 1

    // 計算1: !x
    // x = 0b00000001 = TRUE
    // ! --------------
    //     FALSE or 0

    // 計算2: (!x) | x
    // Lt = 0b00000000 = FALSE
    // x  = 0b00000001 = TRUE
    // | -------------
    //      TRUE or 1

    // よってTRUE(10進数1)になる

    return 0;
}
1
1

[C言語] Cパズルブック 1.4.4 ビット演算子 論理AND

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]

問1.4.4の解法は以下の通りです。

#include <stdio.h>
#define PRINT(int) printf("%d\n",int)

int main() {
    int x , y, z;
    
    // 先頭に0を付けると8進数になる
    x = 03; y = 02; z = 01;

    // 問1.4.4
    PRINT( x & y && z);   // 1

    // ビットAND &を優先
    PRINT( (x & y) && z); // 1

    // 計算1: x & y
    // x = 0b00000011
    // y = 0b00000010
    // & --------------
    //     0b00000010

    // 計算2: (x & y) && z
    // Lt = 0b00000010 != 0 = TRUE
    // z  = 0b00000001 != 0 = TRUE
    // && -------------
    //      TRUE or 1

    // よってTRUE(10進数1)になる

    return 0;
}
1
1

[C言語] Cパズルブック 1.4.3 ビット演算子 ビットXOR

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]

問1.4.3の解法は以下の通りです。

#include <stdio.h>
#define PRINT(int) printf("%d\n",int)

int main() {
    int x , y, z;
    
    // 先頭に0を付けると8進数になる
    x = 03; y = 02; z = 01;

    // 問1.4.3
    PRINT( x ^ y & ~z);   // 1

    // 補数~,ビット積&を優先
    PRINT( x ^ (y & (~z))); // 1

    // 計算1: ~z(zの補数)
    // z = 0b00000001
    // ~ --------------
    //     0b11111110

    // 計算2: y & ~z
    // y  = 0b00000010
    // ~z = 0b11111110
    // & --------------
    //      0b00000010

    // 計算3: x ^ (y & ~z)
    // x  = 0b00000011
    // Rt = 0b00000010
    // ^ --------------
    //      0b00000001
    // ビットXOR:同じ0,違う1
    // よって10進数1になる

    return 0;
}
1
1

[C言語] Cパズルブック 1.4.2 ビット演算子 ビット補数

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]

問1.4.2の解法は以下の通りです。

#include <stdio.h>
#define PRINT(int) printf("%d\n",int)

int main() {
    int x , y, z;
    
    // 先頭に0を付けると8進数になる
    x = 03; y = 02; z = 01;

    // 問1.4.2
    PRINT( x | y & ~z);   // 3

    // 補数~,ビット積&を優先
    PRINT( x | (y & (~z))); // 3

    // 計算1: ~z(zの補数)
    // z = 0b00000001
    // ~ --------------
    //     0b11111110

    // 計算2: y & ~z
    // y  = 0b00000010
    // ~z = 0b11111110
    // & --------------
    //      0b00000010

    // 計算3: x | (y & ~z)
    // x  = 0b00000011
    // Rt = 0b00000010
    // | --------------
    //      0b00000011
    // よって10進数3になる

    return 0;
}
3
3

[C言語] Cパズルブック 1.4.1 ビット演算子 ビットAND

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]

問1.4.1の解法は以下の通りです。

#include <stdio.h>
#define PRINT(int) printf("%d\n",int)

int main() {
    int x , y, z;
    
    // 先頭に0を付けると8進数になる
    x = 03; y = 02; z = 01;

    // 問1.4.1
    PRINT( x | y & z);   // 3

    // ビットAND &を優先
    PRINT( x | (y & z)); // 3

    // 計算1: y & z
    // y = 0b00000010
    // z = 0b00000001
    // & --------------
    //     0b00000000

    // 計算2: x | 00
    // x  = 0b00000011
    // 00 = 0b00000000
    // | --------------
    //      0b00000011
    // よって10進数3になる

    return 0;
}
3
3

[C言語] Cパズルブック 1.4 ビット演算子 

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]

昨年2022年12月以来のCパズルブックです。

コードと出力は以下の通りです。

次回以降、各行について詳説します。

#include <stdio.h>
#define PRINT(int) printf("%d\n",int)

int main() {
    int x , y, z, a;
    
    // 先頭に0を付けると8進数になる
    x = 03; y = 02; z = 01; a = 010;

    printf("前半\n");

    PRINT( x | y & z);   // 3
    PRINT( x | y & ~ z); // 3
    PRINT( x ^ y & ~ z); // 1
    PRINT( x & y && z);  // 1

    // 追加
    PRINT( ~ z);         //-2
    PRINT(a);            // 8 8進数になっているか確認

    printf("後半\n");

    x = 1; y = -1;
    PRINT( ! x | x);     // 1
    PRINT( ~ x | x);     //-1
    PRINT( x ^ x);       // 0
    x <<= 3; PRINT(x);   // 8
    y <<= 3; PRINT(y);   //-8
    y >>= 3; PRINT(y);   //-1

    return 0;
}
前半
3
3
1
1
-2
8
後半
1
-1
0
8
-8
-1

UTF-8で漢字をバイト列に変換

[M1 Mac, Monterey 12.6.3]

漢字はユニコードではU+6F22などという2バイト表記ですが、バイト列にすると3バイトになる仕組みが分からなかったので、エンコード体系を調べて変換の過程をまとめました。

これは情報処理技術者試験にも出題されないような難問奇問かと思います。実務者でないと分からないでしょう。

一応ChatGPTにも聞いてみましたが、かなり分かりにくい説明でした。LLDBデバッガではしれっと嘘を吹き込もうとしたり、プログラミングでもハード寄りの内容になるとあまり使い物にならないです。

[C++] 値渡しと参照渡しの違い ChatGPT

ChatGPTで知識を整理しました。

{
  "messages": [
    {
      "content": "",
      "role": "system"
    },
    {
      "content": "void deleteString(string& str, const char* del) {\n    size_t size = strlen(del);\n    size_t pos = 0;\n    while ((pos = str.find(del, pos)) != string::npos) {\n        // cout << \"pos: \" << pos << endl;\n        str.erase(pos, size);\n    }\n} この関数の引数はなぜ参照渡しなのですか",
      "role": "user"
    },
    {
      "content": "この関数の引数 `str` が参照渡しになっているのは、文字列を削除するために元の文字列を変更する必要があるためです。参照渡しを使用することで、元の文字列を直接変更できます。もし参照渡しを使用しなかった場合、関数内で新しい文字列を作成して返す必要があり、元の文字列を変更することができませんでした。",
      "role": "assistant"
    },
    {
      "content": "他のメモリアドレスに新しい文字列を作成しても問題ないのではないですか",
      "role": "user"
    },
    {
      "content": "新しい文字列を作成しても問題はありませんが、元の文字列を変更するためには、元の文字列のメモリアドレスを知る必要があります。参照渡しを使用することで、元の文字列のメモリアドレスを直接取得できます。一方、新しい文字列を作成する場合、元の文字列のメモリアドレスを知るためには、別途引数として渡す必要があります。また、新しい文字列を作成する場合、元の文字列のメモリアドレスとは異なるメモリアドレスに新しい文字列を作成するため、メモリ使用量が増加する可能性があります。そのため、元の文字列を変更する場合は、参照渡しを使用することが一般的です。",
      "role": "assistant"
    },
    {
      "content": "値渡しでは元の文字列のメモリアドレスは全く分からないのですか",
      "role": "user"
    },
    {
      "content": "値渡しの場合、元の文字列のメモリアドレスは分かりません。値渡しでは、関数に渡された引数はコピーされて関数内で使用されます。そのため、元の文字列のメモリアドレスを知るためには、別途引数として渡す必要があります。しかし、参照渡しを使用することで、元の文字列のメモリアドレスを直接取得できます。そのため、元の文字列を変更する場合は、参照渡しを使用することが一般的です。",
      "role": "assistant"
    },
    {
      "content": "C++以外の言語、例えばPythonでは値渡しになりますがメモリを浪費しているということですか",
      "role": "user"
    },
    {
      "content": "Pythonでは、文字列はイミュータブル(不変)なオブジェクトとして扱われます。そのため、文字列を変更する場合は、新しい文字列を作成する必要があります。Pythonでは、文字列を値渡しで渡すことができますが、文字列を変更する場合は、新しい文字列を作成するため、メモリ使用量が増加する可能性があります。ただし、Pythonでは、ガベージコレクションが実装されているため、不要になったオブジェクトは自動的に解放されます。そのため、メモリ使用量が増加しても、プログラムの実行に影響が出ることは少ないです。ただし、大量の文字列を扱う場合は、メモリ使用量に注意する必要があります。",
      "role": "assistant"
    },
    {
      "content": "これまでの質問回答の内容から15文字程度で日本語タイトルを考えて下さい。英単語が混ざってもかまいません。",
      "role": "user"
    },
    {
      "content": "「参照渡しと値渡しの違いとメモリ使用量」",
      "role": "assistant"
    }
  ],
  "model": "gpt-3.5-turbo",
  "temperature": 0.0
}

[C++] ポインタと参照の違い ChatGPT

たまに混同してしまうので、ChatGPTで知識を整理しました。

要は対象にアクセスするのに、メモリアドレスを格納したポインタという変数を使うか、参照でメモリアドレスに直接アクセスするかの違いです。

ポインタを引数にした関数は引数がNULLでも動きますが、参照を引数にするとメモリアドレスでないと関数を呼び出せません。

デバッガでCPUレジスタやメモリの挙動を追ってみたら明確に理解できると思います。

Q1: オブジェクトへのポインタとオブジェクトへの参照の違いを具体的に教えてください

オブジェクトへのポインタとオブジェクトへの参照は、どちらもオブジェクトを扱う方法ですが、その扱い方に違いがあります。

オブジェクトへのポインタは、メモリ上のオブジェクトのアドレスを指すポインタであり、オブジェクトのメモリ上の位置を示す数値を持ちます。ポインタは、変数や関数の引数、関数の戻り値などに使用され、ポインタが指すオブジェクトに対して操作を行うことができます。例えば、以下のようなC++のコードで、オブジェクトへのポインタを使用して、オブジェクトのメンバ変数にアクセスしています。

class MyClass {
public:
    int x;
};

void foo(MyClass* ptr) {
    ptr->x = 10;
}

int main() {
    MyClass obj;
    foo(&obj);
    return 0;
}

一方、オブジェクトへの参照は、オブジェクトのエイリアスであり、オブジェクトそのものではなく、オブジェクトを参照する変数です。参照は、変数や関数の引数に使用され、参照が指すオブジェクトに対して操作を行うことができます。例えば、以下のようなC++のコードで、オブジェクトへの参照を使用して、オブジェクトのメンバ変数にアクセスしています。

class MyClass {
public:
    int x;
};

void foo(MyClass& ref) {
    ref.x = 10;
}

int main() {
    MyClass obj;
    foo(obj);
    return 0;
}

ポインタと参照の違いは、主に以下の点にあります。

  • ポインタはNULLを設定できるが、参照はNULLになることはない。
  • ポインタはメモリ上のアドレスを持ち、ポインタの値自体を変更することができるが、参照は変数自体がオブジェクトを参照するため、参照自体を別のオブジェクトに変更することができない。
  • ポインタは参照を持つことができるが、参照はポインタを持つことができない。

ポインタと参照はどちらもオブジェクトを扱う方法ですが、その扱い方に違いがあるため、どちらを使用するかは、使用環境や目的によって適切に選択する必要があります。

Q2: 参照のメモリでの挙動を教えてください

参照は、変数が別の変数を参照するための仕組みであるため、メモリ上の挙動は、通常の変数と同様に扱われます。参照変数は、参照しているオブジェクトのエイリアスとして機能し、実際のオブジェクトのメモリ上の位置を指します。つまり、参照変数が指すメモリアドレスは、参照しているオブジェクトのメモリアドレスと同じです。

C++の参照を使用した場合、コンパイラは、参照変数に割り当てられたメモリアドレスを、参照しているオブジェクトのメモリアドレスに自動的にバインドします。そのため、参照変数に対する操作は、参照しているオブジェクトに直接反映されます。例えば、以下のようなC++のコードで、参照を使用してオブジェクトのメンバ変数にアクセスしています。

class MyClass {
public:
    int x;
};

void foo(MyClass& ref) {
    ref.x = 10;
}

int main() {
    MyClass obj;
    foo(obj);
    return 0;
}

この場合、foo関数の引数に参照を使用しています。この参照変数は、obj変数を参照しているため、参照変数を操作することで、obj変数のメンバ変数にアクセスすることができます。参照変数による操作は、オブジェクトそのものに影響を与えるため、obj変数のメンバ変数xに10が代入されます。

参照は、ポインタよりも扱いやすく、より直感的なコードを書くことができます。しかし、参照を使用する場合は、参照先のオブジェクトが常に存在している必要があります。また、参照の設定先を変更することはできないため、設計上の制約がある場合は、ポインタを使用する必要があります。

Q3: 参照しているオブジェクトのメモリアドレスはポインタと呼ばないのですか (あえて聞いた)

参照は、ポインタとは異なる概念であり、メモリ上の挙動も異なります。参照が指すオブジェクトのメモリアドレスは、ポインタが指すオブジェクトのメモリアドレスと同じですが、参照はポインタと異なり、参照変数自体がオブジェクトを参照するため、オブジェクトのアドレスを明示的に取得する必要がありません。つまり、ポインタはオブジェクトのアドレスを指し示す変数であるのに対して、参照はオブジェクトそのものを指し示す変数となります。

C++において、参照とポインタはともにオブジェクトを扱うための機能であり、使い分けによってコードの可読性や扱いやすさを向上させることができます。しかし、その扱い方には違いがあるため、ポインタと参照を適切に使い分けることが重要です。

Q4: オブジェクトを参照する仕組みをハード面から具体的に説明してください

オブジェクトを参照する仕組みは、プログラミング言語によって異なりますが、一般的にはコンパイラやランタイムによってサポートされています。

C++の場合、参照は、変数にエイリアスを割り当てることで実現されます。具体的には、参照変数は、参照するオブジェクトのアドレスを保持しているわけではなく、単に参照するオブジェクトのエイリアスとして機能しています。コンパイラは、参照変数が参照するオブジェクトのメモリアドレスを自動的に割り当て、参照変数が操作されると、参照するオブジェクトのメモリ領域に直接アクセスします。そのため、参照を使用すると、ポインタと比較してプログラムがより簡潔になります。

Javaの場合、参照は、Javaの仮想マシン(JVM)によってサポートされています。Javaでは、参照がオブジェクトを参照するための主要な方法であり、Javaの変数は、基本的に参照であると考えることができます。Javaの参照は、オブジェクトへのポインタのようなもので、参照変数にはオブジェクトのメモリアドレスではなく、オブジェクトの実際の内容が含まれています。JVMは、参照変数が参照するオブジェクトのメモリアドレスを自動的に処理し、参照変数を使用すると、参照するオブジェクトに直接アクセスします。

このように、オブジェクトを参照する仕組みは、プログラミング言語やランタイムによって異なりますが、一般的にはコンパイラやランタイムによって実現されます。

Q5: オブジェクトのメモリアドレスとポインタの違いを教えてください

オブジェクトのメモリアドレスとポインタは、関連があるが、異なる概念です。

オブジェクトのメモリアドレスは、コンピュータのメモリ上でオブジェクトが格納されているアドレスを指します。オブジェクトは、メモリ上の一連のバイト列として表現され、このバイト列が格納されているメモリアドレスをオブジェクトのメモリアドレスと呼びます。オブジェクトのメモリアドレスは、通常は数値で表され、ポインタ変数に代入されたり、関数に渡されたりすることがあります。

一方、ポインタは、オブジェクトのメモリアドレスを保持する変数のことを指します。ポインタは、オブジェクトのメモリアドレスを参照するための機構であり、ポインタ変数は、オブジェクトのメモリアドレスを保持しているため、ポインタ変数を使用してオブジェクトにアクセスすることができます。

ポインタは、通常はアスタリスク(*)を使用して宣言され、ポインタ変数には、オブジェクトのメモリアドレスが代入されます。例えば、以下のようなコードで、ポインタ変数ptrにobjオブジェクトのメモリアドレスを代入しています。

int* ptr = &obj;

このように、ポインタ変数は、オブジェクトのメモリアドレスを保持するための変数であり、ポインタ変数を使用してオブジェクトにアクセスすることができます。

オブジェクトのメモリアドレスとポインタは密接に関連していますが、オブジェクトのメモリアドレスはポインタ変数に代入される値であり、ポインタ変数は、オブジェクトにアクセスするための仕組みであることに注意してください。

Q6: 関数の引数をポインタにするか参照にするか、どういった基準で判断するのですか

C++において、関数の引数をポインタにするか参照にするかは、いくつかの基準があります。以下は一般的な判断基準の例です。

  1. ポインタを使うべき場合
  • NULLを受け入れる必要がある場合
  • メモリの再配置など、ポインタ値を変更する必要がある場合
  • 関数が関数の外部で割り当てられたメモリを更新する必要がある場合
  • 配列を渡す必要がある場合
  1. 参照を使うべき場合
  • NULLを受け入れる必要がない場合
  • 変数の値のみを更新する場合
  • メモリの再配置など、ポインタ値を変更する必要がない場合
  • ポインタを使うとコードが複雑になる場合

以上は一般的な基準であり、関数の用途やプロジェクトの規約によって異なる場合もあります。また、ポインタや参照を使う場合には、その扱い方に注意が必要です。ポインタや参照を適切に扱うことで、プログラムの可読性や安全性を向上させることができます。

Q7: NULLを受け入れる必要がある場合というのは具体的にどういうことですか

NULLは、C++で空のポインタを表す特殊な値です。NULLが使われる場合は、以下のような場合が挙げられます。

  1. ポインタが必須でない場合 関数がポインタを必須としない場合、NULLをデフォルト値として設定することができます。これにより、ポインタ引数を指定しなくても、関数を呼び出すことができます。NULLを許容することで、コードの柔軟性を向上させることができます。
  2. ポインタ引数がオプションの場合 ポインタ引数がオプションである場合、NULLをデフォルト値として設定することができます。これにより、ポインタ引数を指定しなくても、関数を呼び出すことができます。また、NULLを受け取ることで、ポインタ引数を使用しない場合の処理を行うことができます。
  3. ポインタの初期値が不明な場合 ポインタの初期値が不明な場合、NULLを初期値として設定することができます。これにより、初期値を明示的に設定することができます。

以上のように、NULLは、ポインタの値が存在しないことを表現するために使用されます。NULLを受け入れる必要がある場合は、ポインタが必須でない場合、ポインタ引数がオプションの場合、ポインタの初期値が不明な場合などが考えられます。