Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

Cygwinにmonoをインストール


今日もきれいなおねーさんです。結婚してください!!(*´ω`)
癒されますね。
話は変わり、mono使ってみたいなーと軽く調べてみると、
Macはもちろんのこと簡単にWindowsでもmonoが使えるようです。
ただ、Windowsでmonoってクロスプラットフォームなものを作る必要がないなら
いらね……Visual StudioでC#でいいじゃんなので
誰得?な感じはありますが、
コマンドラインで気軽に使えるっていうならいいかも!とおもい……(C#でもできるけど)
入れてみることにしました。
ただ、monoをコマンドプロンプトで使うのはいろいろ検閲により削除
なので、Cygwinで使ってみよーということで調べたらすぐに出てきました。
ちなみにですが、monoとは

MONOは99年、バンドをスタートした時は日本で認められずに、...

間違えました。

Mono(モノ)は、GNOMEプロジェクト創設者のミゲル・デ・イカザが開発した、Ecma標準に準じた.NET Framework互換の環境を実現するためのオープンソースのソフトウェア群、またそのプロジェクト名である。
現在はXamarin社が開発、販売、サポート業務を行っている。
共通言語基盤 (CLI) の実装やC#のコンパイラなどが含まれる。

Cygwin64bit版はいろいろ不備があるのであまり使っている人はいないと思いますが、
いちおう64bit版のやり方を書いておきます。
32bit版もほぼ同じ手順です。

Cygwin64bitのインストール

これは結構面倒です。
とりあえず落としましょう。
ここの説明は…割愛します。
https://www.cygwin.com/

64bit版の場合のmonoインストール

依存するモジュールのインストール

コマンドプロンプトを立ち上げます。
setup-x86_64.exeのあるディレクトリに移動します。
私の環境の場合はC:\CygwinLocalPackageなので

> cd C:\CygwinLocalPackage

Cygwinのインストールディレクトリ(rootになっているディレクトリ)を指定して次のコマンドを打ちます。
私の場合、C:\Cygwin64なので

> setup-x86_64.exe --root "C:\cygwin64" --quiet-mode --packages autoconf,automake,bison,gcc-core,gcc-g++,mingw-runtime,mingw-binutils,mingw-gcc-core,mingw-gcc-g++,mingw-pthreads,mingw-w32api,libtool,make,python,gettext-devel,gettext,intltool,libiconv,pkg-config,git,curl,libxslt

monoをダウンロード(今回はtar.bz2をダウンロードします)

Cygwinを立ち上げてサンプルコードを書きます。
wgetが入っていなければここからダウンロードしてください。
今回は現時点の最新版である4.4.2.30を使います。
Index of /sources/mono

$ wget http://download.mono-project.com/sources/mono/mono-4.2.2.30.tar.bz2

ところでみなさんこれなんて読みますか?僕はノリでウゲットですwww

解凍

最初の2行は変数を設定します。とりあえず公式の解説に合わせて、エンターを都度押してください。
解凍にものすごい時間がかかるのでカップ麺2杯くらいどうぞ(*´ω`)
気分だけ映画のハッカーですw

$ PREFIX=/usr/local
$ VERSION=4.4.2.30
$ tar xvf mono-$VERSION.tar.bz2

コンパイル

みなさん大好きconfigure & makeです。
configureの時間に気が遠くなります……。
カップ麺3杯くらいいけます。
そしてコンパイル・・・もう寝ていいですか(*´ω`)zZZ...

$ cd mono-4.2.2
$ ./configure --prefix=$PREFIX --host=i686-pc-mingw32
$ make
$ make install

--- 2 years later ---
ふぅ……これで環境構築は終わりです。

動作確認

ここでAtCoderの練習問題を解いてみます。

A - はじめてのあっとこーだー(Welcome to AtCoder)
高橋君はデータの加工が行いたいです。
整数 a,b,cと、文字列 s が与えられます。
整数 a+b+c と、文字列 s を並べて表示しなさい。

http://practice.contest.atcoder.jp/
using System;
using System.Linq;
using System.Collections.Generic;
namespace AtCoder
{
    class Program
    {
        public static void Main(string[] args)
        {
            var input = new List<int>();
            input.Add(int.Parse(Console.ReadLine()));
            input.AddRange(Console.ReadLine().Split(' ')
                .Select(int.Parse).ToList());
            string s = Console.ReadLine();
            Console.WriteLine(input.Sum() + " " + s);
        }
    }
}

コンパイル&実行&標準入力を与える…

$ mcs test.cs
$ mono test.exe
1
2 3
test

出力結果

6 test

めでたしめでたし(*´ω`)長かったー。
Cygwinだから異様に遅いんですね(;'∀')
決してPCは低スペックではないですが全部で3時間近くかかりました。
これだとプログラミングコンテストとかで気軽にC#を使えますね!
terminal+viでC#がかけて勉強ができます!
もちろん、特別なことをしない限りIntelliSenseの恩恵にあずかれませんが。

……ここまで書いておいてVM上のLinuxで普通にmono入れりゃあいいだけかも……
なんて少しだけ思いましたw

はてなブログに貼り付けた写真の話

f:id:fa11enprince:20200628235208j:plain
2020年06月28日現在の情報です。
はてなブログから写真をあげている場合(この場合はてなフォトライフに投稿されます)、
長辺が1200pxになるように自動的に圧縮されます。
なので画像サイズは特段意識しなくてもよいかと思われます。

ちなみに1920pxと1600pxをリサイズした時にどちらが綺麗になるのかはよくわかっていません。
なんとなく1920pxのような気もするし、最大公約数が大きい1600pxなのかなという気もしたり…。

以下は過去の話です。
自分でホームページを作成するときなどは画像の大きさや容量を気にしていましたが、
ブログの時って実はあまり気にしていませんでした。
はてなブログの場合、
いいように勝手に圧縮してくれてるのかな?
と思ったらそんなことなかった(;´・ω・)
なので、ブログのトップの画像が1MB超えのものもある……
というありえない状況になっていました。
無駄なトラフィックを発生させてしまい、すみません。
今の時代1Mくらいなんて大したことないですが、
月々使用量が限られているところで契約している人にとっては
思いがけない巨大ファイルは死活問題。
ただでさえ画像貼るな原理主義の人もいるので、
真剣に考えなくてはなりませんね。

こんなことが起こらないようにするためには、
あらかじめ画像ファイルを横幅をせめて1024px以下にする、
できる限り300kB付近にするという対策が必要です。
そのうえでアップロードしましょう。

しかし、そんなことも気づかずに投稿してしまった過去の画像の場合は
正直つらい。
過去の画像を大量に貼り替える……なんてことは、
したくないのでその場合の簡易的な対処法を。
はてなブログの場合ははてなフォトライフに保存されます。
そこで画像の編集ボタンを押すと、
サイズが指定できますのでこれで圧縮をかけます。
画像によりますがおおむね効果があり1/2くらいにはなります。
f:id:fa11enprince:20160131172827p:plain
圧縮!
f:id:fa11enprince:20160131172843p:plain
まぁただこれは応急処置なので、皆さん気を付けましょう。
では(^_-)-☆

他で似たような内容を扱っているブログ

どんな列(幅)でも行数でも読込む関数作りました



C言語を使っているとC++のstring/vectorが使えないせいで
可変長の文字列を含んだファイルを読込むときは
非常に泥臭いことをしないといけない。。。か、もしくは
決めうちで列幅を固定してしまったりすることが多いと思います。

そんなわけでどんなに列幅があってもどんなに行数があっても
簡単に読込める関数を作りました。勉強を兼ねて。
作るところで、簡単にできるだろうと目論んでいましたが、
やってみると盛大にバグりました。
細かいミスがすごい出ました。しかも不具合の原因がすぐにわからない。
久々にC言語のキツさを思い知りました。
低レイヤーの言語って泥臭いけど楽しいですよね。

使い方

こんな感じです。
2次元配列的な構造を列幅、行数を意識せずに取得できるようにしたものです。
もちろん、ヘッダに分離させてソースコードを置いたらincludeとかしないとダメですが。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define SAFE_RELEASE(p) if(p) { free(p); p=NULL; }
// 極端に小さくしていますが、実用上は128とかを設定したいところ。
#define DEFAULT_SIZE 3
// ==== DECLARATION ====
typedef struct Tag_LineData LineData;
typedef struct Tag_StringList StringList;
StringList *getStringList(const char *fileName);
void deallocateStringList(StringList *stringList);
// ==== DEFINITION ====
typedef struct Tag_LineData {
    char *line; 
    size_t length; /* size */
} LineData;
typedef struct Tag_StringList {
    LineData *lineData;
    size_t length; /* rows */
} StringList;

/**
 * 1行を読込み、読み切れなかったら領域拡張して続きを読む関数
 * この関数もっと簡潔に書けないかな…
 */ 
static char *getLineDataRecursive(LineData *lineData, FILE *fp, int depth) {
    // エラー処理のためいったんtmpで拡張
    char *tmp = (char *)realloc(lineData->line, DEFAULT_SIZE * depth * sizeof(char));
    if (tmp == NULL) {
        perror("realloc");
        SAFE_RELEASE(lineData->line);
        exit(1);
    }
    // 初回だけメモリクリア 再帰呼び出し時はクリアするとダメ。
    if (depth == 1) { memset(tmp, 0, DEFAULT_SIZE); }
    lineData->line = tmp;   // tmpを本来拡張したいアドレスに格納
    
    char buf[DEFAULT_SIZE] = { 0 };
    char *ret = fgets(buf, DEFAULT_SIZE - 1, fp);   // 一時用領域に1行読込み(途中なら途中から読込み)
    if (ferror(fp)) {
        perror("read failed");
        exit(1); // おそらく何もできることはないので終了
    }
    strcat(lineData->line, buf);   // 拡張領域に一時用領域の文字列を連結
    // '\n'文字がある場合、
    if (strchr(lineData->line, '\n') != NULL) {
        lineData->length = strlen(lineData->line);
        // '\n'文字を取り除く
        lineData->line[lineData->length - 1] = '\0';
        return ret;  // 改行があるので終わり
    }
    // 改行がない。つまり、DEFAULT SIZEでは必要な分の領域がなく、読み切れなかったということ。
#if _DEBUG
    printf("recursive call===\n");
#endif
    // 読切れなかった(=retに何かしら値がある)場合、再帰呼び出しで領域拡張する
    return ret != NULL ? getLineDataRecursive(lineData, fp, ++depth) : ret;
}

/**
 * 1行読込関数
 */
static char *getLineData(LineData *lineData, FILE *fp) {
    return getLineDataRecursive(lineData, fp, 1);
}

/**
 * 最小限の基本領域確保関数
 */
static StringList* allocateStringList(int lineCnt, FILE* fp) {
    StringList *stringList = (StringList *)malloc(lineCnt * sizeof(StringList));
    if (stringList == NULL) {
        perror("malloc");
        exit(1);
    }
    stringList->length = lineCnt;
    for (int i = 0; i < lineCnt; i++) {
        stringList[i].lineData = (LineData *)malloc(sizeof(LineData));
        if (stringList[i].lineData == NULL) {
            perror("malloc");
            exit(1);
        }
    }
    return stringList;
}

/**
 * 行数取得関数
 */
static int getLineCnt(FILE* fp) {
    int lineCnt = 0;
    LineData lineData = { 0 };
    while (getLineData(&lineData, fp) != NULL) {
        SAFE_RELEASE(lineData.line);
        lineCnt++;
    }
    rewind(fp);
    return lineCnt;
}

/**
 * ファイル読込み&データ取得関数
 */
StringList *getStringList(const char *fileName) {
    assert(DEFAULT_SIZE > 2);
    FILE* fp = fopen(fileName, "r");
    if (fp == NULL) {
        perror("fopen");
        exit(1);
    }
    int lineCnt = getLineCnt(fp);
    printf("lineCnt: %d\n", lineCnt);    
    StringList* stringList = allocateStringList(lineCnt, fp);
    for (int i = 0; i < lineCnt; i++) {
        getLineData(stringList[i].lineData, fp);
    }
    fclose(fp);
    return stringList;
}

/**
 * 領域開放
 */
void deallocateStringList(StringList *stringList) {
    for (int i = 0; i < stringList->length; i++) {
        SAFE_RELEASE(stringList[i].lineData->line);
        SAFE_RELEASE(stringList[i].lineData);
    }
    SAFE_RELEASE(stringList);
}

int main(void) {
    StringList *stringList = getStringList("test.txt");
    for (int i = 0; i < stringList->length; i++) {
        printf("%s\n", stringList[i].lineData->line);
    }
    deallocateStringList(stringList);
    return 0;
}

追記

この記事を書いた後、別のコード例を紹介してくださった記事がありました。

ソースコードを見ると綺麗で無駄がなく、ヘンテコな構造体を定義していなく
普通の用途で使うならこれがいいのでは?
という素晴らしいコードでした。解説もわかりやすいです!

C言語のグローバル変数とexternについて

f:id:fa11enprince:20200628230841j:plain
C言語では言語仕様上、グローバル変数は良く使うと思います。
できるだけ避けるのは言うまでもありませんが。
そこでよく混乱するのがexternではないでしょうか?
ヘッダなんかをインクルードすると
あれ?そういえばexternって……どうなんだっけ…ってことになります。

私なんかはなんかの参考書でプログラミングを学び始めたときに、
externつけてもつけなくても一緒というような怪しげな解説を見た記憶があり、
それによって、余計に混乱してしまいました。
ただし、関数に関しては一緒です。
ここより正確な記載のあるサイトを見つけましたのでURLを記載します。
C言語のexternキーワードについて(関数編) – cloudtofu
いまだに検索流入が多い(2019年8月時点)のでちょっとびっくりします。それだけC言語が息の長い言語であり、
年々使用者が減少しているのでしょうね。私自身もCはもう5年以上触っていません。
いまなら限られた環境でない限りはC++(もしくはGoかRustかもしれない)を使うでしょうね。

externは
他の場所に定義があって、宣言ですよ
って明示するためのものです。

宣言と定義について

厳密な説明ではないのですが、
C言語における宣言とは値や中身がかかれていないものです。
例えば、

int g_value;
extern int g_value2;
int foo(void);

は宣言です。
一方、定義は

int g_value = 0;
int foo(void) {
    return g_value;
}

などです。

グローバル変数を使うときどうすればいいか、
基本的にヘッダ側(.h)はextern付の宣言をして、.cファイルのどこかに
externなしの定義を書きます。その際に初期値を代入します。
これでほぼOKです。
もちろん、.h側にexternなしの変数宣言をしてはいけません。
ヘッダファイルにはいろいろお作法があるのですが、
きちんと書かれているものが少ないように思われます。
そういうわけで巷には間違って書かれている
ヘッダファイルがあふれているのではないでしょうか?

ただし、これでは定義がどこにあるのか、
しかも一つでなくてはならないので、管理が複雑になり、混乱します。
しかも仮定義という厄介な概念があり、もっと事情は複雑です。

こうしておけばよい

最初に最終版を書きます。
GLOBAL_VALUE_DEFINEDをmainのあるファイルにdefineし
マクロでextern有無しを制御します。
初期値を0以外に指定したいときはやはりマクロで制御します。

test.h

#ifndef TEST_H_INCLUDED_
#define TEST_H_INCLUDED_

#ifdef GLOBAL_VALUE_DEFINE
  #define GLOBAL
  #define GLOBAL_VAL(v) = (v)
#else
  #define GLOBAL extern
  #define GLOBAL_VAL(v)
#endif

GLOBAL int g_value;   // この場合は最初の定義で0で初期化
/* GLOBAL int g_value GLOBAL_VAL(1); */

void foo(void);

#endif /* TEST_H_INCLIDED_ */

test.c

#include "test.h"

void foo(void) {
    g_value++;
}

myapp.c

#define GLOBAL_VALUE_DEFINE
#include <stdio.h>
#include "test.h"

int main(void) {
    printf("%d\n", g_value);
    foo();
    printf("%d\n", g_value);
    g_value++;
    printf("%d\n", g_value);
    return 0;
}

Makefile

あとコンパイルがやや面倒なのでMakefileを書きます。
Makefileの解説をすると長くなるので割愛します。

CC           = gcc
CFLAGS       = -Wall
DEBUGFLAGS   = -O0 -D _DEBUG -g
PROG         = myapp
SOURCES      = myapp.c test.c
OBJS         = $(SOURCES:.c=.o)
INCDIR       =
LIBDIR       =
LIB          =

.PHONY: all
all: $(SOURCES) $(PROG)

# Primary Target
$(PROG): $(OBJS)
        $(CC) $(CFLAGS) $(DEBUGFLAGS) -o $@ $^ $(INCDIR) $(LIBDIR) $(LIB)

# Suffix Rule
.c.o:
        $(CC) $(CFLAGS) $(DEBUGFLAGS) -c $< $(INCDIR) $(LIBDIR) $(LIB)

.PHONY: clean
clean:
        $(RM) $(OBJS) $(PROG)

コンパイル&リンク

$ make
gcc -Wall  -O0 -D _DEBUG -g -c myapp.c
gcc -Wall  -O0 -D _DEBUG -g -c test.c
gcc -Wall  -O0 -D _DEBUG -g -o myapp myapp.o test.o

実行結果

$ ./myapp.exe
0
1
2

解説

結論はわかったとしてどうしてこうするのかというのを説明します。
話を単純化するために例えば次のような2つのファイルがあるとします。

test.c

extern int g_value;

void foo(void) {
    g_value++;
}

myapp.c

#include <stdio.h>

int g_value = 0;
/* extern */ void foo(void);

int main(void) {
    printf("%d\n", g_value);
    foo();
    printf("%d\n", g_value);
    g_value++;
    printf("%d\n", g_value);
    return 0;
}

もちろんこれは次のようにコンパイルすればちゃんと問題なく動きます。

コンパイル

gcc -Wall  -O0 -D _DEBUG -g -c myapp.c
gcc -Wall  -O0 -D _DEBUG -g -c test.c
gcc -Wall  -O0 -D _DEBUG -g -o myapp myapp.o test.o

補足ですが関数定義はexternがあってもなくても外部結合(=ファイルの外から見える)ので
あってもなくてもよいです。

ここで、test.c側のextern int g_value;
int g_value = 0;
に書き換えると、当然

test.o:test.c:(.bss+0x0): `g_value' が重複して定義されています

というようなエラーがでます。
つまり、.hファイルに単純にくくりだしてint g_value = 0;
として両者の.cでincludeすると同じことが起こります。
そのようなことを防ぐために最初のようにマクロで制御しています。

しかし、厄介なのがANSI Cの仮定義という概念で、
externをかかず、いろんなファイルでint g_value;とした場合は……

test.c

int g_value; /* 他に定義がないので int g_value = 0;の定義として扱う */
...

myapp.c

#include <stdio.h>

int g_value; /* 既にg_valueの定義があるので、extern int g_value;(宣言)として扱う */
...

これはなんとコンパイルが通ります。理由は上記コードのコメント部分です。

その他constのグローバル変数について

constにも同じことが言えます。
ただし、constでexternかどうか気にしたことがないぞ?っていう人もいるかもしれません。
……そもそも伝統的なCを使っている人は#defineを使っているかもしれませんが。
実はconstの場合、CとC++で微妙に振る舞いが違います。
externを使わない場合、
Cの場合は外部リンケージ
C++の場合は内部リンケージとなります。

参考リンク