2006年01月28日

C++初級: deleteの直後に[]を付ける理由

通常 new[] で確保したメモリは delete [] で開放する必要がある。

【例】
 char* p = (char*)new char[100];
  :
 delete [] p;

ここで「delete [] p;」を「delete p;」と書いてしまうとメモリがリークする可能性がある。
(優秀なコンパイラだとコンパイル時に警告してくれるみたい。)
(C++Builderの場合 CodeGuard で検出可能。)

これは、明示的に [] を付けて配列をメモリ開放することをコンパイラに伝えないと、配列の先頭だけがメモリ開放されるためである(C++標準仕様より)。

・・普通に考えたら、配列で確保したメモリなのだから、配列として開放するのは当たり前かも・・。


・・・もしよかったら、右上のバナー広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m


posted by かねやん at 20:30| Comment(0) | TrackBack(0) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2006年01月03日

C言語: 符号ビットのシフトに失敗!

昔、ビットシフトの演算処理を行った際、ちょっとハマったことがある。

#include <stdio>
void main(void)
{
 short value1, value2;

 value1 = value2 = 0x8000;

 value1 >>= 1;
 value2 = value2 >> 1;

 printf("value1 = %x\n", (unsigned short)value1);
 printf("value2 = %x\n", (unsigned short)value2);
}

bit_shift_src.zip
bit_shift_exe.zip

上記のプログラムを実行した場合、

  value1 = 4000 
  value2 = 4000 


が出力されるものと思っていたが、
実際には以下のように出力された。
bit_shift_image.JPG


思惑としては「0x8000」の最上位1ビットが右シフトすることによって
0x4000」になるはずだった。

この不具合の原因としては、符号付シフトが行われたことによって、符号ビットがコピーされていたことが原因となる。

これは、ビット演算の処理において符号付の変数を使用していたという初歩ミスであり、変数の型を「short」から「unsigned short」に修正するだけで解決する。

同じケースに遭遇することは滅多に無いと思われるが、みなさんも気をつけよう!

それでは〜。


・・・もしよかったら、右上のバナー広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 02:00| Comment(4) | TrackBack(2) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年10月10日

C++バグ削減: コピーコンストラクタを忘れるな!

C++でクラスを作成する際、コピーコンストラクタが必要な場合がある。
うっかり作り忘れてしまうとバグってしまうことにつながる。

次の例では、コピーコンストラクタを作り忘れて、不正なメモリにアクセスするパターンである。
copy_constractor_ng.cpp
#include <iostream>
#include <string>

using namespace std;

class CLASS1
{
 protected:
  char* data;

 public:
//コピーコンストラクタ
//(自分がコピーされる時に呼び出される)
// CLASS1(const CLASS1& src) {
//   data = new char[64];
//   strcpy(data, src.data);
//   cout << "Call CopyConstructor" << endl;
// }

  CLASS1() { data = new char[64]; }
  ~CLASS1() { delete [] data; }

  void SetData(const char* newdata) {
    strcpy(data, newdata);
  }
  void Display() {
    cout << "data = " << data << endl;
  }
};

void main()
{
  CLASS1 c1;
  c1.SetData("C1");
  c1.Display();
  //「C1」を表示

  {
    CLASS1 c2 = c1;
    //c2.data と c1.data は
    //同一のデータ領域を指す

    c2.SetData("C2");
    c2.Display();
    //「C2」を表示

    c1.Display();
    //上記によって書き換えられた為
    //「C2」を表示
  }
  //(c2.data消滅 = c1.data消滅)

  c1.Display();
  //ゴミデータが表示される
}


この例では、コピーコンストラクタが無い為に「c1.data」と「c2.data」のデータ領域が同じ場所を指す形になってしまう。
ソース内において、コピーコンストラクタのコメントをはずすと正常に動作する。

・・・こんな感じで、コピーコンストラクタの実装には気をつけよう!


・・・もしよかったら、右上の広告バナーをクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 03:56| Comment(18) | TrackBack(6) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年10月07日

C++バグ削減: コンストラクタ内ではオーバーライドされない!

C++のクラスにおいて、コンストラクタ内で仮想関数を呼び出した場合、想定外の動作となる。
・・・コンストラクタ内で仮想関数を呼ぶことなんてあまりないかもしれないが・・・。

その例を以下に示す。
constractor_virtual.cpp
#include <iostream>

using namespace std;

class CLASS1
{
 protected:
  int value;

 public:
  CLASS1() { Initialize(); }
  void SetValue() { Initialize(); }

  virtual void Initialize() { value = 1; }
  virtual void Display() { cout << value << endl; }
};

class CLASS2 : public CLASS1
{
 public:
  CLASS2() {}
  virtual void Initialize() { value = 2; }
};

void main()
{
  CLASS2 c2;
  c2.Display();
  //コンストラクタ内ではオーバーライドされない為
  //「1」が出力される

  c2.SetValue();
  c2.Display();
  //メンバ関数では正しくオーバーライドされる為
  //「2」が出力される
}


・・・この例からも分かるとおり、コンストラクタ内では仮想関数を呼ばないように気をつけよう!


・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 01:42| Comment(2) | TrackBack(2) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年10月06日

C++バグ回避: virtualの付け忘れで誤動作!

C++でクラス設計を行う際、virtual を付け忘れることで想定外の動作となってしまうことがある。

たとえば次のよな場合である・・・。

sample_ng.cpp
//-------------------------------------------------
#include <iostream>

using namespace std;

//-------------------------------------------------
class CLASS1
{
 protected:
  int num;

 public:
  CLASS1() { num = 0; }
  void SetNum() { num = 1; }     // 誤: virtualなし
// virtual void SetNum() { num = 1; } // 正: virtualあり
  void DisplayNum() {
   cout << "num = " << num << endl;
  }
};

//-------------------------------------------------
class CLASS2 : public CLASS1
{
 public:
  CLASS2() {}
  void SetNum() { num = 2; }
};

//-------------------------------------------------
void main()
{
  //---------------------------------------------
  CLASS1 c1;
  c1.SetNum();
  c1.DisplayNum();
  // num = 1 が表示される

  //---------------------------------------------
  CLASS2 c2;
  c2.SetNum();
  c2.DisplayNum();
  // num = 2 が表示される

  //---------------------------------------------
  CLASS1* c2p = (CLASS1*)new CLASS2;
  c2p->SetNum();
  c2p->DisplayNum();
  // CLASS1 の SetNum() に virtual の定義が無い為
  // 思い通りにオーバーライドされず
  // ポリモーフィズムが実現できない
  //  ↓ ↓ ↓ ↓
  // num = 1 が表示される
}



・・・以上のことから、仮想関数にする場合は virtual を忘れないように気をつけよう!

今回のケースと関連して、デストラクタに対する virtual は、特に忘れないように気をつけよう!


・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 01:33| Comment(19) | TrackBack(2) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年10月05日

C/C++バグ予防: マクロ関数の定義方法に注意!

define定義によるマクロ関数を作成する際、うっかり括弧を付け忘れると、想定外の結果になる場合がある。

たとえば次のようなケースである。
#include <stdio.h>

#define FUNC(a) a+a

void main()
{
  int value = FUNC(2) * 2;
  printf("Result=%d\n", value);
}


実行結果として「Result=8」と表示されそうなのだが、実際は「Result=6」となる。

これは内部でFUNC()が展開されると「value = 2 + 2 * 2;」となる為である。

次のようにFUNC()の定義を修正することで正しい結果が得られる。
#include <stdio.h>

#define FUNC(a) (a+a)

void main()
{
  int value = FUNC(2) * 2;
  printf("Result=%d\n", value);
}


ちなみに次のように記述すると「(value++ + value++) * 2」と展開される為、valueが2回インクリメントされてしまうので注意が必要だ。

#include <stdio.h>

#define FUNC(a) (a+a)

void main()
{
  int value = 2;
  int result = FUNC(value++) * 2;

  printf("value = %d\n", value);
  printf("result = %d\n", result);
}



・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 00:22| Comment(6) | TrackBack(5) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年10月03日

C/C++バグ予防: 安易にゼロを付けないようにしよう!

プログラムを組む上で、ソース中に数値を直書きするケースは多々ある。
(あまりよろしくないことだが・・・。)

その際、安易に「0」を付けた事により、想定外の動作になるケースを紹介する。

たとえば次のようなケースでは、VALUE_010の値が「10」ではなく「8」になってしまう。

sample_ng.cpp
#include <stdio.h>

const int VALUE_001 = 001;
const int VALUE_010 = 010;  //(←先頭の0は不要)
const int VALUE_100 = 100;

void main()
{
  printf("VALUE_001=%3d\n", VALUE_001);
  printf("VALUE_010=%3d\n", VALUE_010);
  printf("VALUE_100=%3d\n", VALUE_100);
}


sample_value0_image.JPG

このケースでは、ついついソースをキレイにそろえようと「10」の前に「0」を付けてしまったことが不具合の原因となる。

これは数値の先頭に「0」を付けることにより、その値は8進数として認識される為である。


・・・そんなわけで安易に「0」を付けないように気をつけたいところだ。


・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 00:31| Comment(3) | TrackBack(1) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年10月02日

C/C++バグ予防: ポインタを返す関数で気をつけること

過去に以下のような関数を見たことがある。

#include <stdio.h>
#include <stdlib.h>

char* func1(int val)
{
  char strtmp[16];

  sprintf(strtmp, "%d", val * 2);

  return (char*)&strtmp;
}

void main()
{
  printf("result=%s", func1(10));
}


これはローカル変数 strtmp のスコープが外れて不定値を返してしまうバグの例である。
結果、画面には「result=data」と表示されない場合が多い。
(実行環境やメモリ配置などの状況によっては「result=data」と表示されることも考えられる。)

改善方法としては、以下のように strtmp を引数で渡す方法が考えられる。

#include <stdio.h>
#include <stdlib.h>

void func1(int val, char* strtmp)
{
  sprintf(strtmp, "%d", val * 2);
}

void main()
{
  char strtmp[16];
  func1(10, strtmp);
  printf("result=%s", strtmp);
}


また、strtmpを外に出してグローバル変数にしたり、static変数として宣言する方法もあるが、これはまた新たなバグの危険性が潜んでいる。

たとえば、次のようなケースである。

#include <stdio.h>
#include <stdlib.h>

char* func1(int val)
{
  static char strtmp[16];

  sprintf(strtmp, "%d", val * 2);

  return (char*)&strtmp;
}

void main()
{
  printf("result=%s,%s", func1(10), func1(20));
}


実行結果として「result=20,40」を期待するのだが「result=20,20」となってしまう。


・・・そんなわけでポインタで値を返す関数を作成する場合は注意が必要だ。


・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 00:01| Comment(1) | TrackBack(1) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年09月30日

C/C++バグ防止: 「==」と「=」を間違えるな!

C/C++にて if 文を記述する際、「==」を「=」と間違って記述してしまい、バグを盛り込んでしまうケースがある。

本当は次のように記述するつもりが・・・
if (a == 1) {
 ;
}


次のようにコーディングミスをしてしまうケースである。
if (a = 1) {
 ;
}


しかも、コンパイルはエラーにならない為、普通に実行することができてしまう。
(コンパイル時に警告を発してくれるコンパイラもある。)


C/C++の他にVBやDelphiなども使用するユーザーは特に注意が必要かもしれない。
(VBやDelphiでは「==」ではなく「=」と記述する。私も時々間違えそうになる。)

しかし、本件は次のように記述することで容易に回避できる。

if (1 == a) {
 ;
}


もし間違えて次のように記述してもコンパイル時にエラーとなり、
本件の不具合を回避できる。

if (1 = a) {
 ;
}



・・・それでは、今回はこんな感じで〜。(^_^)/


・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 02:45| Comment(3) | TrackBack(4) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年09月29日

C/C++バグ防止: defaultのスペルミスに気をつけろ!

今回は、switch文の中で default を使用する時のバグについて紹介する。

まずは下記のソースを見て欲しい。
#include <iostream>
using namespace std;

void main()
{
 int index = -1;
 cin >> index;

 switch (index)
 {
  case 0: cout << "0" << endl; break;
  case 1: cout << "1" << endl; break;
  case 2: cout << "2" << endl; break;
  defualt: cout << "other" << endl; break;
 }
}


パッと見た感じは問題なさそうだが、実は「default」のスペルが間違っている。

スペルミスした「defualt:」はラベルとして認識される為、これでもプログラムは動くものの、当然ながら「default」としては機能しない。


このミスを防ぐためには以下の点が有効と考えられる。
・ソース編集時のエディタで「default」などの予約語を着色や強調表示させる。
・スペルミスをチェックするなどの机上デバッグを行う。
・他者からソースをレビューしてもらう。

・・・そんな感じでバグには気をつけよう!


・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 00:32| Comment(21) | TrackBack(6) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年09月27日

C++でガベージコレクション

今回は、C++でガベージコレクションを実現する方法を紹介する。

通常、newで確保したメモリは、使用後にdeleteでメモリを開放する。

【例1 (normal.cpp)】
#include <stdio>
#include <stdlib>
#include <string>
#include <iostream>

using namespace std;

struct DATA1 {
  char str[128];
};

void func1(char* str)
{
  DATA1* data1 = new DATA1;

  sprintf(data1->str, "Random Value = %d", rand());
  strcpy(str, data1->str);

  delete data1;
}

void main()
{
  randomize();

  char str[128];
  func1(str);
  cout << str << endl;
}


しかし、STL(Standard Template Library) の auto_ptr を使うことにより、スコープから抜けた時点で確保したメモリを自動的に開放してくれる(ガベージコレクション)。

【例2 (auto_ptr.cpp)】
#include <stdio>
#include <stdlib>
#include <string>
#include <iostream>

using namespace std;

struct DATA1 {
  char str[128];
};

void func1(char* str)
{
  auto_ptr data1((DATA1*)new DATA1);

  sprintf(data1->str, "Random Value = %d", rand());
  strcpy(str, data1->str);
}

void main()
{
  randomize();

  char str[128];
  func1(str);
  cout << str << endl;
}


auto_ptr を使用することにより、メモリリーク発生のリスクを軽減できると思われる。


・・・もしよかったら、右上の広告をクリックしてほしいな・・・。
・・・もしくは、人気blogランキング をクリックしてほしいな・・・。
 m(_ _)m
 
posted by かねやん at 00:01| Comment(1) | TrackBack(3) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年09月26日

画期的な「虫除けスプレー」が欲しいな・・・。

C++でプログラムを組むと1発でコンパイルが通ることは なかなか無い(・・・私だけ!?)。

それどころか、コンパイルをすり抜けた虫(バグ)が実行ファイルの中に寄生する・・・。

その虫をキレイさっぱり退治するのがどんなに大変か・・・。

すぐに出てくる虫は即効退治できるが、隠れて出てこない虫には手を焼いてしまう・・・。


シュッと、ひと吹きするだけで虫を予防できるような、「ソフトウェア向けの虫除けスプレーでも無いかな・・!?」と思ってしまう今日この頃である。


・・・と、そんなボヤきは置いといて、さっきこのブログに「C/C++言語」のカテゴリを追加した。
このカテゴリでは、C/C++言語による開発時のバグを回避する方法や、効率よく開発する方法ついて掲載していきたい。

あと、昔に比べるとソフトウェアにおける「品質」は厳しい目で見られるようになってきたと感じている。
ささいなバグのせいで、時として損害賠償を喰らう可能性があることを肝に銘じておきたいところだ。
posted by かねやん at 01:00| Comment(3) | TrackBack(0) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

2005年09月25日

「NaN」とはナンだ!?

あなたは、C言語のプログラムにおいて「NaN」という文字列に遭遇したことはないだろうか?
熟練したプログラマはご存知かもしれないが、初めて遭遇した時は「なんじゃこりゃーっ!?」って感じだ。
ちなみに「NaN」とは、インドカレーのときに出てくるもののことではない...。

NaN」とは、実数演算にて0除算を行った結果、出力される文字列で「Not a Number」の略である。

例えば、以下のコードを実行した場合に「NaN」が出力される。
(0除算の例外を出力するコンパイラは対象外)

◆ソース
#include <stdio.h>
void main(void)
{
  double a = 0, b = 0;
  printf("%f\n", a/b);
}


◆実行例
 nan_image.jpg

実数の演算結果を文字列に変換したつもりが、数字の文字列ではなく「NaN」という文字列が出力される。
初めて遭遇する人にとっては驚く事態だ。


よくある例として、C言語初心者に「電卓のプログラム」を作るように指示した場合、0除算を考慮されないケースが多いような気がする。

運良く私は 0除算のミスをしたことが無いものの、基本中の基本なので今後も気をつけたいところだ。


・・・これらの情報が良かったと感じたら、右上の広告をクリックしてほしいな・・・。m(__)m
 

p.s.
 今回は出張の為、とある宿泊先からの接続なのだが、なかなかつながらず...。
 つながれっ!このやろー!で、やっとつながったのでした。
 (トーンかと思ったらパルスだった)。
 みなさん、そんなことない!?
posted by かねやん at 23:45| Comment(3) | TrackBack(1) | C/C++言語 | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。