Gobble up pudding

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

MENU

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

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++の場合は内部リンケージとなります。

参考リンク

Linuxを使いたいけど、vi(Vim)って難しいよね。できれば使いたくない!……ってひと向けnanoエディタ


Linuxって使うのにいろいろハードルがあるのですが、
その一つの大きな障壁がコマンドライン上でのテキスト編集作業ではないでしょうか?
デフォルトで用意されていてかつメジャーなのが
viというこれまた変な操作体系のエディタで
最初使ったらなんじゃこりゃ。入力できないし、編集できないし……。
終了の仕方がわからず、そっとターミナルを閉じた……。
なんて人は多いんじゃないでしょうか?僕です。
第一、viなんてよっぽどのモノ好きでないと使いこなせるようになりません。
というか必要に迫られないと覚えられないようなものです。
Linuxが使いたいだけなのに変な操作体系を覚えさせられる…。
おれはLinuxをやりたいんだ。
でもなぜかviの学習をしている……。ってなんだかおかしいですよね。
GUIが整っている環境なら、geditというエディタを使って編集という手もありますが、
なかなかそうもいかず、ターミナル(黒い画面)経由でアクセスしてなんてことも多いですよね。
また、VPSなんか使って最小構成にしていると、GUIすらないし……。
ああ、もう、FTPかSCPでファイル取ってきてWindows上で編集してまたあげるか……
それは過去の僕です。
やっぱりviか……となるんですが、ターミナル上でもメモ帳感覚で使えるエディタがあるのです!
それがnanoエディタ。CentOSならだいたい最初から入っていることが多いです。
もしない場合は下記コマンドでインストールできます。(ルートユーザでやってね!)

使ってみよう

# yum install nano

起動は次のようにします。ファイルネームを引数にしてあげます。

$ nano [filename]

今回はtestというファイル名でファイルを作ってみるので

$ nano test

でエンターダンッ!
CentOSのターミナル上でつかうとこうなります(Ubuntu/Macでも使えるよ!)。

日本語環境をちゃんと整え切っていないので英語になっていますが、
日本語にも対応できます。
基本的にメモ帳感覚で使えます。
保存がCtrl+Oなのがちょっと奇妙ですが……。
もちろんvi同様カスタマイズができて、homeに.nanorcを置いて
何か書いてやればカスタマイズも可能です。
ちなみにviな人が使って油断するとjjjjjkkkkkとか思わず入力してしまいます。
※私はVimmerです

関連記事