C/C++」タグアーカイブ

Dynamic array Length

GLib 関数で得た gchar** つまり文字列の配列の要素数。
ってよく考えたら Vala は foreach できるじゃないか。

/* valac -C test.vala */

class Test {
    public static int main(string[] args) {
        var l = Environment.list_variables();
        foreach (var s in l) {
            stdout.printf("%s\n", s);
        }
        return 0;
    }
}

g_get_environ のバインドが何故か無いので g_getenv バインドで。
コレがビルドできるということは Vala は要素数を得ているということ。
ということで毎度のように -C オプションしてチェック。

gint 
get_array_length (gchar** array) {
	gint length = 0;
	if (array)
	    while (array[length])
		    length++;
	return length;
}

これだけ?
配列がメモリ上でどうなっているか知っていれば確かに納得だけーが。
(char**) つかヒープ領域つか動的配列専用になってしまうな。

(char[ ][ ]) のスタック変数には絶対に使ってはいけない。
コッチはゴロゴロ見付かる方法で、と使い分けてください。
(char**) (char[ ][ ]) の両対応ははたして可能なのか?

とにかくテストコードを書いて確認。

#include <glib.h>

gint 
get_array_length (gchar** array) {
	gint length = 0;
	if (array)
	    while (array[length])
		    length++;
	return length;
}

gchar**
create_array(void) {
    gchar** arg;
    arg = (gchar**)g_malloc0(sizeof(gchar*) * 4);
    arg[0] = g_strdup("YAMAHA");
    arg[1] = g_strdup("HONDA");
    arg[2] = g_strdup("KAWASAKI");
    arg[3] = g_strdup("SUZUKI");
    return arg;
}

int
main (int argc, char *argv[]) {

    gchar** array;
    gint i, count;

    array = create_array();
    count = get_array_length(array);
    g_printf("count=%d\n", count);
    for (i=0; i<count; i++) {
        g_printf("%s\n", array[i]);
    }
    g_strfreev(array);

    return 0;
}

dynamic_array_length

上記は g_malloc0 でゼロ詰めしてセグメントエラーは一応回避している。
でも実際にアプリに使うとなると少し不安な感じ。

arg[2] = NULL;

等には完全に無力なわけで。
でもそれは配列を作る側の問題。

確実にいえるのは GLib 関数の戻り値に使うかぎりはコレで問題ない。
自前動的配列に使う場合はチョッピリ注意しよう。

g_strjoinv

ヒープ上に配列を作ってそのポインタを sizeof() してもポインタサイズしか得られない。
だとすると char** が戻って来るこんな関数の場合はどうなるんだ?

#include <glib.h>

int
main (int argc, char *argv[]) {

    gchar** env_array;
    int i;
    long count;

    env_array = g_get_environ();
    count = sizeof(env_array) / sizeof(env_array[0]);
    g_printf("count = %ld\n", count); //=> count = 1
    i = 0;
    for (i; i<count; i++)
        g_printf("%s\n", env_array[i]);
    g_strfreev(env_array);

    return 0;
}

だよね、両方 8byte になるから 1 という結果に。
手段は無いかな、検索検索。
動的にメモリを確保した配列の要素数を調べるには 【OKWave】
自作配列なら自分で管理すればいいけど関数で戻って来るのはお手上げか。

Python なら list で得られるから単純にループでイケるのだが。

python_env

まてよ、上記のように全部出力する場合なら Python の

"\n".join(env_arraya)

みたくできれば問題なくね?

#include <glib.h>

int
main (int argc, char *argv[]) {

    gchar** env_array;
    gchar* s;

    env_array = g_get_environ();
    s = g_strjoinv("\n", env_array);
    g_printf(s);
    g_free(s);
    g_strfreev(env_array);

    return 0;
}

strjoinv

こんなにアッサリ。
Python の join をバカにしている人、GLib ならむしろ自然だぞ。
破棄は g_strfreev に全部おまかせできるようです。

単純に全部くっつけるだけならコレでいいけど加工になると…
GList や GArray に変換は自力でやるしかなさそう。

GLib を使っても C 言語で文字列を扱うのは面倒臭いのは変わらない。
楽したけりゃ Python や Vala を、という結論で。

sizeof

sizeof ってスタック領域でのサイズだったのか!

#include <glib.h>
#include <stdio.h>

int
main (int argc, char *argv[]) {

    gchar* s;

    /* コレはイケる */
    gchar c[] = "たのしい Linux 1";
    g_printf ("%s は %ld 文字です\n", c, g_utf8_strlen(c, sizeof(c)));

    /* sizeof(s) が 8byte、つまりポインタのサイズになってしまう */
    s = g_strdup("たのしい Linux 2");
    g_printf ("%s は %ld 文字です\n", s, g_utf8_strlen(s, sizeof(s)));
    g_free(s);

    /* 一応試したけど同じだった */
    s = g_malloc(256);
    g_sprintf(s, "たのしい Linux 3");
    g_printf ("%s は %ld 文字です\n", s, g_utf8_strlen(s, sizeof((gchar*)s)));
    g_free(s);

    /* strlen ならイケるけど警告になる */
    s = g_strdup("たのしい Linux 4");
    g_printf ("%s は %ld 文字です\n", s, g_utf8_strlen(s, strlen(s)));
    g_free(s);

    /* Best */
    s = g_strdup("たのしい Linux 5");
    g_printf ("%s は %ld 文字です\n", s, g_utf8_strlen(s, -1));
    g_free(s);

    return 0;
}

sizeof

gcc でしか試していないけど、まあ思いっきり GLib だし。
(gchar*) キャストも無意味ってことはつまりそういうことだろう。
ひらがな UTF-8 は 3byte だから当然 2 文字分しか検出しない結果に。

検索しても何も見つからないのは何故だろう?
gcc 以外ならイケるのか、それとも皆スタック領域でしか sizeof を使わないのか。

てゆーかこの問題は -1 指定で普通に解決した。
おかげでこんなことが解ったので無駄な時間は結果オーライ。

GLib stdin

時は 2015 年

@ サイト管理者やプログラマー
今後は更にスマートフォンの時代になる、タッチパネル向けに作らなければ!

@ 絵師
ソシャゲ用イラストの需要が凄い、ソッチ向けを考慮しなきゃ!!

@ 一般的な人
モンストと Line で忙しい、おっと便利なサイト発見etc…

@ ネクラでキモチワルイ人達
スマホで充分という人はパソコンでもたいしたこと(以下略)ドャァ!!!

こうなることは解っていたがスピードが速すぎな気がする。
実際の話、仕事に必要という理由以外でパソコンを買う人は減る一方なのは確実。
筆者は Fedora 愛用だが実は他人には Mac かタブレットを勧めている。
Linux なんてサイトを自力で作っている人以外にはメリットが無いので。

確実なのは Windows は既にデファクトスタンダードでは無いということ。
モバイルでの Web に関してはもはや Safari がスタンダードに近い。

大型バイク乗りのオッサン、もういいかげんにビクスクを認めろよ。
確かに大型バイクはカッチョイイけどそんなものオイラ達には不要なんですよ。
PCX150 がバカ売れしている時点で余程のバカでなければ気が付くはずなのだが。
その「余程のバカ」が多すぎる、スマホ普及率をガン無視するネラー共と同じく。

絵に関しては Mac 使いにまかせる、その理由で Mac にしたはずだし。
こんな時代にパソコンを使うならプログラミングは必須。
でないとネクラでキモチワルイ人達の一人だと思われてしまう。

そんなこんなで GLib Tips のページを作ろうと思ったわけですが。
GLib って scanf, fgets, fgetc という標準入力のラッパーが無いんですね。

scanf は色々アレなので fgets を今迄使っていたのだが。
コレも色々ある、バッファオーバーランに無力だしスタック領域が無駄だし。
何より改行コード付随というのが気に入らない。

#include <stdio.h>
#include <string.h>
 
int
main (int argc, char *argv[]) {
    char line[256];
    printf("Hi!\nPlease enter your name: ");
    fgets(line, sizeof(line), stdin);
    /* Remove '\n' */
    line[strlen(line)-1] = '\0';
    printf("Welcome, %s!\n", line);
    return 0;
}

ライブラリ関数で取得したものを自力で再加工なんて無駄にもほどがある。
ならば最初から自力で取得し加工したほうが無駄がない。

/* valac -C src.vala */
stdout.printf ("Hi!\nPlease enter your name: ");
var name = stdin.read_line ();
stdout.printf ("Welcome, %s!\n", name);

Vala には stdin.read_line() という便利な関数がある。
コレをどう C 言語にジェネレートしているか -C オプションで確認。
なんと自前で関数を作って適用するという恐るべし手段を使っていた。
相変わらず最適化依存のナニコレなコードを出力するが省略するとこういうこと。

#include <glib.h>
#include <stdio.h>

/* gcc src.c `pkg-config --cflags --libs glib-2.0` */

static gchar*
iostream_read_line (FILE* p) {

	GString* gstr;
	gchar* result;
    gint n;

	gstr = g_string_new("");
	while (TRUE) {
	    n = fgetc(p);
        if (n == ((gint)'\n') || n == EOF) {
			break;
		}
	    g_string_append_c(gstr, (gchar)n);
    }
    result = g_strdup(gstr->str);
    g_string_free(gstr, TRUE);
    return result;
}

int
main (int argc, char *argv[]) {

    gchar* line;

    g_printf("Hi!\nPlease enter your name: ");
    line = iostream_read_line(stdin);
    g_printf("Welcome, %s!\n", line);
    g_free(line);

    return 0;
}

glib_iostream

fgetc で 1 byte ずつチェックしヒープ領域に積み立てて最後にポインタを戻す。
なるほど、これならスタック領域の無駄も最小限だしバッファオーバーランの心配も皆無。
日本語入力も半角スペースも何一つ問題なく処理できてしまう恐るべき関数であった。
Vala を作っている人達は想像以上に頭がいいようです。

ライブラリ関数に徹底的なほど拘るのも悪くないと思うんですけど。
強引に利用するより自分で関数を作ったほうが建設的だなと。
つか、ライブラリ関数に拘る人ってモレなく何もアプリを作っていない現実がね。

g_list_foreach

STL(C++) の std::list と GLib の GList で foreach してみた。
C と C++ の微妙な違いがあってなかなか興味深い。

C++, STL

#include <iostream>
#include <list>
#include <algorithm>

void
show_data(std::string s) {
    std::cout << s << std::endl;
}

int
main (int argc, char **argv) {

    std::list<std::string> l;
    std::string s;

    for (int i=0; i<3; i++) {
        std::cout << "input " << i << " : ";
        std::cin >> s;
        l.push_back(s);
    }
    // for_each @ algorithm
    std::for_each(l.begin(), l.end(), show_data);
    return 0;
}

GLib

#include <glib.h>

void 
show_data(gpointer data ,gpointer user_data) {
    g_printf("%s\n",data);
}

void 
delete_data(gpointer data ,gpointer user_data) {
    g_free(data);
    data = NULL;
}

int
main (int argc, char **argv) {

    GList* list = NULL;
    gchar s[256];
    gint i;

    for (i=0; i<3; i++) {
        g_printf("input %d : ", i);
        scanf("%s", s);
        //list = g_list_append (list, s); // All Last Data
        list = g_list_append (list, g_strdup(s));
    }
    /* foreach */
    g_list_foreach(list, show_data, NULL);
    /* free */
    g_list_foreach(list, delete_data, NULL);
    g_list_free( list );
    return 0;
}

ビルド結果は同じものが作られるはず。

glist

std::for_each, g_list_foreach 共に関数ポインタを使っている。
GLib はソックリにしようと思ったのでしょうか?
STL は関数オブジェクトが使えるとかもろもろは置いておいて。

ところで for 文等を使わずにズラズラ並べるなら当然下記でもいい。
スタックのメモリ領域なので当然破棄処理も不要。
実際のアプリケーションでこんなコードになることはまずありえないですが。

list = g_list_append (list, "YAMAHA");
list = g_list_append (list, "HONDA");

for 文でコレをやると当然だけど全部同じ s ポインタを参照になる。
なのでデータを拾う毎にヒープに領域を作ってそのポインタを入れていく必要あり。
当然破棄も必須、C++ の std::string は全自動でやってくれているだけだし。

ただ知ってのとおり std::list は整数による添字アクセスができない。
というか何をするにもループ必須で正直使い勝手がよくない。
GList は一応こんなことができるようです。

gchar* c;
/* 3 番目を表示 */
c = (gchar*)g_list_nth_data(list, 2);
g_printf("%s\n", c);
/* 3 番目を削除 */
c = (gchar*)g_list_nth_data(list, 2);
list = g_list_remove(list, c);

STL を少し知っているなら直感的に書くことが可能、かつちょっぴり便利。
でも class は使えないからデストラクタで破棄とかはできないよってか。
むりやり std::list と同じにしようとしたのではないようで面白い。