Gobble up pudding

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

MENU

わかりにくい関数ポインタを返す関数

f:id:fa11enprince:20200624215158j:plain
関数ポインタって変な文法ですよねってお話です。
もちろんポインタの文法もいろいろツッコミどころはあるのですが
(このせいでポインタよくわからんな人が続出)。
例えば次の宣言はぱっと見すぐにわかる人はC言語マスターです。

void (*func(const char *str)) ();

これはvoidを返すのではなく、
void (*)()という関数ポインタを返す、
funcという名前のconst char *strを引数にとる関数です(;'∀')

(void (*)()) func(const char *str);

のような書き方が許されていれば誰も混乱しないのに……

コードを書いてみます。
gccでコンパイルしてみました(VC++だともっとincludeしないとダメかも)

#include <stdio.h>

void ps1()
{
    puts("banana");
}
void ps2()
{
    puts("apple");
}
void ps3()
{
    puts("orange");
}
void (*func(const char *str)) ()
{
    if (strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    // こう書いても同じ
    //void (*test)() = func("ps1");
    //(*test)();
    
    func("ps2")();
    func("ps3")();
    return 0;
}

実行結果

banana
apple
orange

これtypedefを使うともっとましになるよってことで書いてみます。

#include <stdio.h>

typedef void (*FUNC_PS)();

void ps1()
{
    puts("banana");
}
void ps2()
{
    puts("apple");
}
void ps3()
{
    puts("orange");
}
FUNC_PS func(const char *str)
{
    if (strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

多少マシになりましたね。
ただ、見て分かる通り、ここでも問題が、
typedefがわかりにくい……
なぜ、typedef FUNC_PS void(*)();ではないのだ……
これならだれもこんがらがらないのに……

じゃあ、そんな書き方できるのあるよ!!ってことでC++11ならば

#include <cstring>
#include <cstdio>
#include <cstdlib>

using FUNC_PS = void (*)();

void ps1()
{
    std::puts("banana");
}
void ps2()
{
    std::puts("apple");
}
void ps3()
{
    std::puts("orange");
}
FUNC_PS func(const char *str)
{
    if (std::strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (std::strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (std::strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

これならわかりやすい!!
ということでこれならOKでしょう。

ただし、C++11使うならstd::functionを使いましょう。

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <functional>

void ps1()
{
    std::puts("banana");
}
void ps2()
{
    std::puts("apple");
}
void ps3()
{
    std::puts("orange");
}
std::function<void()> func(const char *str)
{
    if (std::strcmp(str, "ps1") == 0)
    {
        return ps1;
    }
    else if (std::strcmp(str, "ps2") == 0)
    {
        return ps2;
    }
    else if (std::strcmp(str, "ps3") == 0)
    {
        return ps3;
    }
    return NULL;
}

int main()
{
    func("ps1")();
    func("ps2")();
    func("ps3")();
    return 0;
}

世の中にはもっと変態的な宣言がありまして
signalが有名ですね

void (*signal(int sig, void (*func)(int))) (int);

Σ(゚д゚lll)!!!?
もうチョイわかりやすく書くと…

void (*signal(
    int sig, /* シグナル番号 */
    void (*func) (int) /* シグナルハンドラ */
)) (int);

これは
void (*)(int)を返す
signalという名前のintとvoid (*func)(int)を引数にとる関数です。
typedefしてあるとなんてことはない…

typedef void (*sighandler_t)(int);
sighandler_t signal(int signo, sighandler_t func);

typedefの文法がもっとましならよかったのに…

using sighandler_t = void (*)(int);
sighandler_t signal(int signo, sighandler_t func);

だったらよかったのに……
というかこれならだれも混乱せずに読めるのに

(void (*)(int)) signal(int sig, void (*func)(int));

Parallels Desktop for MacでExcelを快適に使うための設定

Parallels Desktop for Macの紹介

MacにはBootCampというのがあってWindowsを別パーティションにいれて、
起動を切り替えて使うことができますが、
いろいろ問題があります。まず、切り替えるのに再起動が必要なこと。
これだけでも結構不便なのに、さらに問題はツールでも入れない限り、
Windowsでスクロールの挙動が逆になったり、キーボードがとても使いづらくなったり……
というのがあります。
この状況を一挙に解決してくれるソフトがParallels Desktop for Macです。
これはVMware Playerと一緒で仮想環境でWindowsを動かします。
仮想環境といっても通常の使い方をする範囲では遅さを全く感じません。
なにより同時に扱えるところが良いです。
また、デフォルトでCoherenceモードといって、
ほぼMacとWindowsをシームレスに使うことができます。
やや高いソフトですが、どうしてもOffice文章を扱わないといけない
といったことがあれば、Mac版Windowsを買うよりこちらがおすすめです。
もちろん別途Officeのライセンスは必要ではありますが。
また、デフォルト設定で主要なユーザフォルダ以下が透過的に見えます。
そして、一番嬉しいのがWindows上でもトラックパッド、キーボードの操作感が
変わらないことです。

Excelを快適に使うための設定

Excelで作業をしていると、Ctrl+↓系の最後の行まで一気に飛ぶ系のキーボードショートカットが
効かず、かわりにウィンドウが最小化してしまう現象に遭遇しました。
Ctrl+Shift+↓も同様でききません。
ということでキーボードショートカットを設定します。

設定方法

このParallelsのウィンドウが出ている状態で、環境設定を選びます。


環境設定のウィンドウが出たらキーボードのタブを選択します。

そして左側からWindowsの欄を選び、下側の+を押してキーボードショートカットを追加します。
まずはCtrl+↓の例です。

次にCtrl+Shift+↓の例です。

同じ要領で↑、←、→について同様の設定をすると、Excelでよく使う
終端までの移動等が使えるようになります。

その他調べてないこと

一つだけ課題があって、MSのIMEとMacのIMEが衝突して、
うまくキーボードで切り替えられないところでしょうか。
まだ、調べていませんが、
おそらくキーボードショートカットの割り当てでなんとかなる気がしています。
とおもったら、ここにヒントが書いてありました。
http://kb.parallels.com/jp/115294

C言語の不可解なエラー(GCC) - 配列の宣言時にconst変数を指定したとき

f:id:fa11enprince:20200628230957j:plain
stackoverflowで英語の練習を兼ねてダメダメ英語を連投してました。
そのうち迫害されるんじゃないかと思います(*´Д`)
そんなわけで、Cのプログラムを書いていたのですが……
不可解なエラーが……

問題になったコード

#include <stdio.h>
#include <string.h>

const int MAXLINE = 1000;

// Dummy Array
static char fooString[2][MAXLINE] = {
   "abcdefg\n",
   "hijklm\n"
};

// Dummy function
int myGetline(char **line, int maxline) {
    static int i = 0;
    char *tmp = fooString[i++];
    if (strlen(tmp) > maxline) {
        *line = '\0';
        return 0;
    }
    *line = tmp;
    return strlen(tmp);
}

int main(int argc, char *argv[]) {
    char *line = NULL;
    long lineno = 0;
    // ...
    return 0;
}

コンパイル

$ gcc -o foo.exe foo.c
foo.c:9:13: エラー: ファイルスコープの可変 ‘fooString’ です
 static char fooString[2][MAXLINE] = {
             ^
foo.c:10:4: 警告: char の配列にとって初期化子文字列が長すぎます
    "abcdefg\n",
    ^
foo.c:10:4: 警告: (‘fooString[0]’ 用の初期化付近)
foo.c:12:1: 警告: char の配列にとって初期化子文字列が長すぎます
 };
 ^
foo.c:12:1: 警告: (‘fooString[1]’ 用の初期化付近)

ハッ?全く意味が分からないエラーだ……
そういえばCの古いコンパイラって配列の宣言に
const使えないんだっけってことを30分後に気付く。

対策法1

gccで不当ならg++でC++としてコンパイルでエイッ!

$ g++ -o foo.exe foo.c

コンパイル成功。

対策法2(だめでしたー)

いやいやC++じゃなくてCで何とかしろよって場合は

$ gcc -std=c99 -o foo.exe foo.c
foo.c:9:13: エラー: ファイルスコープの可変 ‘fooString’ です
 static char fooString[2][MAXLINE] = {
             ^
foo.c:10:4: 警告: char の配列にとって初期化子文字列が長すぎます
    "abcdefg\n",
    ^
foo.c:10:4: 警告: (‘fooString[0]’ 用の初期化付近)
foo.c:12:1: 警告: char の配列にとって初期化子文字列が長すぎます
 };
 ^
foo.c:12:1: 警告: (‘fooString[1]’ 用の初期化付近)

あれ?C99はOKなんじゃなかったっけ?

対処法3

まぁこれが妥当ですね。諦めて#defineを使う

#include <stdio.h>
#include <string.h>

// const int MAXLINE = 1000;
#define MAXLINE 1000

// Dummy Array
static char fooString[2][MAXLINE] = {
   "abcdefg\n",
   "hijklm\n"
};

// Dummy function
int myGetline(char **line, int maxline) {
    static int i = 0;
    char *tmp = fooString[i++];
    if (strlen(tmp) > maxline) {
        *line = '\0';
        return 0;
    }
    *line = tmp;
    return strlen(tmp);
}

これでコンパイルが通ります。
C言語はこれだから嫌だ……。
妙に古臭いところが残ってるんですよね…。いい言語には違いないのですが。
好きなC++はそれはそれでSTLで長編小説なエラーが出るという……。

Windows 10のスタートメニューの格納場所など

f:id:fa11enprince:20190926123426j:plain
しばらくWindows 8を使っていて、スタートメニューの格納場所を忘れてしまいましたのでメモ。
Windows 10に移行してから
ここにアンインストールしたはずのソフトのゴミが残っているので掃除。
一応レジストリ周りも見直しています。放置してもよいのだけれど。
あれれProgramDataだっけ?AppDataだっけになってしまっていました。
ProgramDataもAppDataもよくよく見るとゴミだらけになっていますね。
まぁ、別にいいんだけれども(;^ω^)

スタートメニューの格納場所

Windows 7と変わらず

C:\ProgramData\Microsoft\Windows\Start Menu\Programs

Explorer上のデフォルト表示では
PC(C:) > ProgramData > Microsoft > Windows > スタートメニュー > プログラム
です。
スタートメニューはユーザ個別ではなく、全ユーザで一緒です。

おまけ

Quick Launchの場所

C:\Users\[ユーザ名]\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch

なんでInternet Exploererなんだろう……
そういえばいつだかIEとExplorerが合体してた時期がありましたね……。
Exploerer上でshell:quick launchでもOK

参考リンク

http://www.sicnet.co.jp/useful/faq/003.php
Windows 7/8/8.1にクイック起動を表示させる方法 - ぼくんちのTV 別館

Windows 10を使うにあたって絶対使ってはいけない機能があります。
デスクトップのスライドショーです。
奥さんにとがめられても知りません。
Windows 8のメトロUIでもありましたけど。
奥さんがいない上級者の方は私を含め、使っても大丈夫です。