MA-SATANのエンジニアブログ

とある大阪の日本酒Loveなエンジニアの開発ブログ

C/C++ 配列変数の宣言時の初期化方法あれこれ

配列変数時の宣言方法を整理してみました。
C言語の配列変数の初期化方法は複数ありますが、意味は同じです。こういうのをシンタックスシュガーと言うみたいです。charの多次元配列なんかは、時々書き方を忘れるので復習、復習。。。

数値型の1次元配列の宣言時の初期化

#include <stdio.h>

int main(int argc, char* argv[])
{
    int number1[]  = {1, 2, 3};
    int number2[3] = {4, 5, 6};

    printf("number1=%d,%d,%d\n", number1[0], number1[1], number1[2]);
    printf("number2=%d,%d,%d\n", number2[0], number2[1], number2[2]);

    return 0;
}

=== 実行結果 ===
number1=1,2,3
number2=4,5,6

ちなみに、要素数を指定して初期化する場合に、要素数以上の値を入れると警告が出ます。実行時にnumber[2]などの範囲外の値を見ると、代入した値は入っていません。

int main(int argc, char* argv[])
{
    int number1[]  = {1, 2, 3};
    int number2[2] = {4, 5, 6};  /* 配列数2で宣言しているのに、3個代入している */

    printf("number1=%d,%d,%d\n", number1[0], number1[1], number1[2]);
    printf("number2=%d,%d,%d\n", number2[0], number2[1], number2[2]);

    return 0;
}

=== コンパイル結果 ===
In function ‘main’:
警告: 配列初期化子内の要素が多すぎます
警告: (near initialization for ‘number2’)

文字型(char)の1次元配列の宣言時の初期化

鍵かっこ({})を付ける方法、ダブルクォート("")を付ける方法の2通りあります。
素数を書略した場合は、最後に'\0'を挿入してくれます。要素数を指定する場合、最後の'\0'を考慮したサイズを設定するように!

#include <stdio.h>

int main(int argc, char* argv[])
{
    char str1[]  = "1abcd";
    char str2[]  = {'2', 'a', 'b', 'c', 'd'};
    char str3[6]  = "3abcd";
    char str4[6] = {'4', 'a', 'b', 'c', 'd'};

    printf("str1=%s\n", str1);
    printf("str2=%s\n", str2);
    printf("str3=%s\n", str3);
    printf("str4=%s\n", str4);

    return 0;
}

=== 実行結果 ===
str1=1abcd
str2=2abcd
str3=3abcd
str4=4abcd

多次元配列の宣言時の初期化

一番迷うのが多次元配列の宣言&初期化ではないでしょうか。
読み取り専用データであれば、要素数を省略する書き方が楽ですね。

#include <stdio.h>

int main(int argc, char* argv[])
{
    char str1[3][6] = {"1abcd", "12345", "zzzzz"}; /* 要素数を指定 */
    char str2[][6]  = {"2abcd", "12345", "zzzzz"}; /* 1次元目の要素数を省略 */
    char *str3[6]   = {"3abcd", "12345", "zzzzz"}; /* charポインタ、配列の組合せ */
    char *str4[]    = {"4abcd", "12345", "zzzzz"}; /* charポインタ、配列の組合せ */

    int num1[][3]   = { {1,2,3}, {100,200,300}, {-1, -2, -3} };

    printf("str1[0]=%s\tstr1[1]=%s\tstr1[2]=%s\n", str1[0], str1[1], str1[2]);
    printf("str2[0]=%s\tstr2[1]=%s\tstr2[2]=%s\n", str2[0], str2[1], str2[2]);
    printf("str3[0]=%s\tstr3[1]=%s\tstr3[2]=%s\n", str3[0], str3[1], str3[2]);
    printf("str4[0]=%s\tstr3[1]=%s\tstr3[2]=%s\n", str4[0], str4[1], str4[2]);

    printf("num1[0] = %d,%d,%d\n", num1[0][0], num1[0][1], num1[0][2]);
    printf("num1[1] = %d,%d,%d\n", num1[1][0], num1[1][1], num1[1][2]);
    printf("num1[2] = %d,%d,%d\n", num1[2][0], num1[2][1], num1[2][2]);
    return 0;
}

=== 実行結果 ===
str1[0]=1abcd   str1[1]=12345   str1[2]=zzzzz
str2[0]=2abcd   str2[1]=12345   str2[2]=zzzzz
str3[0]=3abcd   str3[1]=12345   str3[2]=zzzzz
str4[0]=4abcd   str3[1]=12345   str3[2]=zzzzz
num1[0] = 1,2,3
num1[1] = 100,200,300
num1[2] = -1,-2,-3

俺は利き酒師になる!

もっと日本酒を知りたい!伝えたい!という思いが抑えきれずついに利き酒師を目指すことにしました。
この資格はどちらかというとお酒の提供側の資格で、日本酒に関する知識はもちろんのことですが、お客さんにあった日本酒を提案出来る、お酒の楽しさや美味しい飲み方を伝える為の資格です。
なので、私のようなエンジニアが目指すのは少し目指すらしいのかな?www.ssi-w.com

どうやったら利き酒師になれるの?

利き酒師の講座を受講、またはテキストで学習後にペーパー試験を受けて合格すると利き酒師になれます。尚、試験はペーパーのみでテイスティングの試験はありません。
利き酒師のさらにうえの資格である「酒匠」や「日本酒学講師」の試験ではテイスティング試験があるようです。

それで、利き酒師の講座は会場で直接受けたかったのですが、ほとんどが東京会場でしか受講できないので、在宅受講のコースを洗濯することにしました。
費用は57,800円です。結構高いですね。。。

利き酒師の教材が到着!

在宅受講を申し込んで10日くらいで教材が届きました。
我が家の猫も興味津々です!

  • テキスト3冊(日本酒の基、もてなしの基、テイスティングノート)
  • DVD 4枚(日本酒の基:4枚、もてなしの基:1枚)
  • 教材酒 (薫酒、爽酒、醇酒、熟酒、劣化酒)

f:id:ma-satan:20150425143352j:plain
f:id:ma-satan:20150425143437j:plain

試験は2か月後

私の場合2か月後の試験日を選択しました。というか大阪会場での試験日は限られているので必然的にその日を選ぶしかなかった。

まだ少ししか教材を見ていないですが、かなりのボリュームがあります。ですがお酒の歴史や日本酒造りに関する細かな情報がたくさん書かれており読んでいて楽しいです。

この資格と取るとどうなる?

私の場合は仕事とまったく関係がないので、完全に自己満足の世界です。ただ、今よりもっと物思いにふけりながらお酒を飲むことが出来ると思うと幸せです。

日本酒が好きなみなさま!この資格をとってはどうでしょうか?

Xperia Z Ultra SIMフリー版を購入

愛用していたNexus7のタッチ感度が非常に悪くなってきた為、新たな機種の購入を決意!
7インチくらいの端末を探してたんだけど意外と少ないことに驚き。
世の中の人はみんな5インチくらいの端末でネットしているのだろうか。

Xperia Z Ultra VS iPad mini3

この2機種で悩みました。
いろいろ比較しましたが、やはりiPad mini3はスペックしょぼいくせに高いです。私はアップル信者ではないので中身と値段で決めました。

項目 Xperia Z Ultra iPad mini3
CPU 2.2GHz(4コア) A7 1.3GHz(2コア)
Memory 2GB 1GB
ストレージ 32GB 16GB/64GB/128GB
液晶サイズ 6.4インチ 7.9インチ  
価格 31,550円 + 消費税 56,800円 (16GB/Cellular版)

※2015/4/10当時の情報
Xperiaの価格はexpansys の価格

開封の儀

Expansysで海外からの購入の為、4/8に注文して4/19に到着しました。Expansysから身分証の確認等を求められ、少しめんどかったです。そして、海外での購入の為、消費税は家に到着時に支払うよう求められます。この辺りの情報を調べずに注文した人は驚いたでしょうね。。。

さて開封の儀の始まりです。

箱は思ったより小さいです。
f:id:ma-satan:20150425121806j:plain

箱を開けると、本体、イヤホン、USBケーブル、電源のコネクタ?が入っています。
電源のコネクタはもちろん海外仕様なので日本では使えません。
f:id:ma-satan:20150425122232j:plain

本体は非常に薄くてスタイリッシュです。
大きさがわかるように書籍と並べてみます。
f:id:ma-satan:20150425122441j:plain

合わせてケースも買いました。
Xperiaのスタイリッシュさ失わず、カードも入る利便性もGoodな感じです。
Amazon.co.jp: Sony Xperia Z Ultra Low Profile Leather Wallet Case by Covert - (Black) 国内正規品: 家電・カメラ
f:id:ma-satan:20150425122728j:plain
f:id:ma-satan:20150425122807j:plain
f:id:ma-satan:20150425122910j:plain

今のところの問題点

購入して2週間使った感想として、大きさや持ちやすさ、処理にもたつきがなく操作感もかなり良いです。
ただ以下2つだけ気になりました。

  • 勝手にタッチが反応する。
  • よくわからないSonyアプリが多い。

特に1番目のタッチが勝手に反応する問題は深刻で、たまに設定が勝手に変わっています。
同様の事象が他のユーザーでも報告されてますね。Xperiaの端末全般に発生する問題でしょうか。
画面を触っていないのに画面が勝手に動く時の対処法 | Xperia ZL2の世界一わかりやすいレビューと使い方

C言語 constを外してみる

当たり前ですが、constで定義した値を書き換えるようなことをしてはいけません。
constの復習と無理やり書き換えようとした場合の動作を確認してみます。

constのパターン

constの使い方として3パターンあります。

1. 定数データ

ポインタが指しているデータを定数にします。
データは変更不可だが、ポインタは変更可能。

int main(int argc, char* argv[])
{
    const char* value_01 = "neogeo";   /* 定数データ */
    const char* value_02 = "genesis";

    value_01[0] = 'x';        /* コンパイルエラー */
    value_01    = value_02;   /* セーフ */
    return 0;
}

=== コンパイル結果 ===
エラー: 読み取り専用位置 ‘*value_01’ への代入です
2. 定数ポインタ(その1)

ポインタを定数にします。
 ポインタは変更不可だが、データは変更可能。
 ※ただし、ポインタが指すデータが読み取り専用であれば、データの変更は不可です。

int main(int argc, char* argv[])
{
    char data[]          = "nes";
    char* const value_01 = data;   /* 定数ポインタ */

    value_01[0] = 'x';             /* セーフ */
    value_01    = data;            /* コンパイルエラー */

    printf("value_01=%s\n", value_01);
    return 0;
}

=== コンパイル結果 ===
エラー: 読み取り専用変数 ‘value_01’ への代入です
3. 定数データ&定数ポインタ

データもポインタも定数になります。

int main(int argc, char* argv[])
{
    const char* const value_01 = "vita";     /* 定数データ&定数ポインタ */
    char* value_02             = NULL;

    value_01[0] = 'x';     /* コンパイルエラー */
    value_01 = value_02;   /* コンパイルエラー */
    return 0;
}

=== コンパイル結果 ===
エラー: 読み取り専用位置 ‘*value_01’ への代入です
エラー: 読み取り専用変数 ‘value_01’ への代入です
4. constを外してキャストしてみる

定数データを通常のcharへキャストして、データを変更してみます。
結果はコンパイルは通りますが、変更するデータが読み取り専用位置にあるデータであればセグメンテーションエラーが発生します。
value_02のようにスタックに配置されてるデータであれば、変更は可能です。

int main(int argc, char* argv[])
{
    const char* data01  = "hello";     /* 定数データ   */
    char data02[]       = "good bye";  /* 通常のデータ */

    /* 定数ポインタ */
    const char* value_01 = data01;
    const char* value_02 = (const char*)data02;

    /* これはコンパイルエラー
    value_01[0] = 'x';
    value_02[0] = 'y';
    */

    /* constを外してキャストする */
    char* value_03 = (char*)value_01;
    char* value_04 = (char*)value_02;

    value_03[0] = 'x';    /* データ変更 NG 実行時エラー */
    value_04[0] = 'y';    /* データ変更 OK */

    printf("value_01=%s\n", value_01);
    printf("value_02=%s\n", value_02);

    return 0;
}

trelloでタスク管理をしよう

4月も第2週が過ぎ、京都は春爛漫といった感じです。
日常生活は過ごしやすい季節ですが仕事はぐったりです。山積みの課題、高い目標設定、前年度の赤字決算によるモチベーションの低下。などと愚痴を言っていても始まらないので、1つずつ課題を改善することにします。

タスク管理で作業効率をあげよう

前年度を振り返ってみると、どのチームも仕事量が多くオーバーワークでした。ですが、もっと作業時間は削減できたと思います。
例えば、以下のように無駄な作業が多かったです。

  • 認識違いによる仕様変更・使用追加の発生
  • 車輪の再発明(既にあるツールをよく作っていた)
  • 最終的に使われないものを開発
  • ゴールが曖昧で、いつまでも開発している。

このような問題を早期に除去できれば、さっさと家に帰ることが出来ます。早く帰るためにもタスク管理に力を入れることにしました。

タスク管理ツールtrello

タスク管理の手法を探していたところ、世の中では「かんばん駆動開発」なるものが流行りつつあるようです。
【かんばん駆動開発】
http://www.slideshare.net/tayasu/20140524-35097952

調べていくと「trello」というタスク管理ツールを使えば、私のやりたかった以下のようなことが実現出来そうです。

  • 全体の状況の把握
  • タスクを可視化して管理、定期的な整理
  • メンバー間のスムーズな情報共有

使ってみるとシンプルなUIで、タスクも可視化できて非常に使いやすいです。ホワイトボードに付箋を貼り付けているタスクを並べるようなイメージですね。
メンバー管理も簡単で良い感じなので、「trello」を使うことにしました。

タスク管理の運用方法 スクラム

さて、ツールがどんなに素晴らしくても、運用方法が下手だと結局はタスク管理出来ません。このあたりは開発手法や開発プロセスの検討を行う必要ありそうです。
最近の開発手法としては、アジャイル開発が様々提唱されていますが、XP、スクラム、EVOなどの既存の手法を、そのまま取り入れると失敗することが目に見えています。
なので、アジャイル開発のエッセンスの一部を取り入れて、自分の組織にあった開発手法を作ることにしました。
主にスクラムの手法の使えそうな部分を実践してみることにしました。
バックログの作成
 各プロジェクトのゴール設定、仕様整理、優先度整理、タスクの洗い出し
・定期的なプロジェクトの見直し
 2week毎に各プロジェクトのバックログ、タスクを見直す
スクラム会議
 毎朝20分の定例ミーティングの実施
・振り返り
 月末に振り返りミーティングを実施、発生した課題の改善方法の検討、その他作業効率を上げるための方法検討

一週間実践してみてよかったこと

まだ1週間ですが、良い効果はあったと思います。


・タスクが可視化によって「誰が・何を・いつまでに」が明確になる。
 特にメンバー全員が複数のプロジェクトを担当しているので、プロジェクト間のタスクの優先度を意識しながら作業が出来るようになりました。

・毎朝のミーティングで課題の早期解決
 20分という短い時間ですが、実のあるディスカッションが出来ます。大変だと思っていた課題も、人に話すと一瞬で解決することが多いです。
・作業の進捗がよくなった。気がする。
 タスクは1~2日で出来るものに分解し、完了予定日を本人に決めてもらいます。 なので、毎朝の定例で完了したかどうか本人に問い詰めています(笑)。軽いプレッシャーを与えるせいか、作業の進み具合がいつもより早い気がします。

trelloによるタスク管理の難しいところ

・タスクの粒度設定が難しい
私の会社は自社製品の開発で、複数の業務案件、研究開発案件、不具合修正依頼など多数のプロジェクトが同時に走っています。そのような場合のtrelloでのboard・cardへ分解する粒度が難しいです。
boardを分けすぎると全体が把握しづらくなり、大きく分けると個々のプロジェクト進捗がわかりにくくなっていしまします。
・カードの設定単位、工程の設定が難しい。
 基本は、Todo -> Doing -> Doneの工程で進みますが、研究開発案件と不具合修正案件では開発の工程が大きく異なります。案件にあった工程・カード設定が難しいです。

まとめ

  • trelloによるタスク管理とスクラム風開発スタイルは効果あり。
  • だが、タスクの粒度設定や工程の設定が難しい

まだ、1週間なので実践&検証を繰り返しながら洗練させていきたいと思います。

C/C++ ヨーダ記法ってダメなの?

ヨーダ記法ってご存知でしょうか?
if文で定数を左辺に書く記法のことです。

/* ヨーダ記法 */
if( 10 == count ) {
    return 1;
}

/* 通常の書き方 */
if( count == 10 ){
    return 1;
}

何のためにヨーダ記法を使うのか?>>

ヨーダ記法で書くと比較(==)を誤って代入(=)でコーディングした場合でも、
コンパイルエラーで発見できるので、不具合を事前に発見できることがメリット。

if( 10 = count ) { /* "=="ではなく"="になっている */
    return 1;
}

何でヨーダ記法はダメなの?

世間一般的には、ヨーダ記法は過去のもので否定派が大多数のようです。
ヨーダ記法がダメだと言われる理由は以下
①直感的ではない。(わかりにくい)
②英文として不自然。
③ワーニングを有効にすれば発見できる。

リーダブルコードでも、以下のように書かれています。

現代のコンパイラはif (obj = NULL)と書くと警告を出してくれる。したがって、「ヨーダ記法」は過去のものになりつつあると言えるだろう。

ヨーダ記法が許される条件

個人的には組み込み系などでは、ヨーダ記法使っても良いと思います。
組み込み系では移植性や安全性が求められるので。

なので、以下のような理由から、組み込み系ではいいのではないでしょうか?
①実行環境・移植先の環境のCコンパイラで警告が出る保証がない。
 組み込み系のプログラムでは、移植性が非常に重要でどんな環境で利用されるかわかりません。
 したがって、必ず警告が出る保証もない。
②もし、不具合が出てしまった場合、甚大な被害が出てしまう。
 またプログラムの更新が非常に困難。

また、仮にワーニング出ても100%修正するとは限らないしね。
警告見てない環境や人間もいるので。。。


過去に組み込み系の製品で不具合出たときは、社員総動員で莫大なコストと時間がかかりました。
もう二度と不具合は出すまいと思いました。。。
そのような経験すると、絶対的にプログラムの読みやすさよりも安全性が重要です。




C/C++ 配列の処理途中での初期化

仕事でプログラム組んでると、構造体の初期化は時々悩みます。
構造体宣言時に初期化する時は、以下のように書けば良いかと思います。

typedef struct hoge_ {
    int   a;
    float b;
} hoge;

hoge data[10] = {0};


ただ、メンバなどに持っている配列を、処理途中にその都度初期化したい場合があると思います。
みんなどうしてんの?って気になったのでググったが、明快な答えが意外に少ない。

見たところ3パターンありそうです。

①愚直にループで初期化

構造体のメンバ数が少ない、配列数が少ない場合はまだいいけど、配列数が増えると処理が遅いですね。
また、後からメンバが増えると初期化漏れが発生する可能性があるので、あまりよろしくないかと思います。

for(i = 0; i < 10; i++) {
    data[i].a = 0;
    data[i].b = 0.0;
}

②memsetで初期化

この方法で初期化している人は多いと思います。
構造体のメンバが整数値、ポインタのみならいいけど、floatなんか入っていると、処理系によっては0でクリアされないですね。
移植性を気にしなければ、いいかも。

memset(data, 0, sizeof(hoge)*10);

③初期化済みデータを代入

私はこの方法でやってます。
いちいち初期化データ容易するのがめんどいですが、移植性考えてこの方法でやってます。

hoge init_data = {0, 0.0};

for(i = 0; i < 10; i++) {
    data[i] = init_data;
}

個人的には③でやってますが、頻繁にCallされて巨大な配列の場合は処理速度が気になるので②のmemsetも検討して使ってます。

もっとスマートな方法あれば教えてください。。。。