実開発に使われるC言語 [基礎から裏技まで] (2) Why C?? コンパイラの力
-
2回目のスレッドですが、今回はみんなさんにCを学ぶ積極性を少しでも上げたいのと、IDEツール依存症から少しでも解放させたいです。
<1>. 今の時代に、どうしてCを学ぶ/使う必要があるのか?
それは、i-focusのロゴの「技術で世界を変える」想いを実現するためだと、私は考えています。「誰でもできるもののみをいくら作っても、結局何も変えられない」、と私は思います。「世界を変える」ためには、やはりGoogleなどの最強の会社と比べないとなりませんから。
これらの会社は何を作っているのかを見てみると、開発ツールやフレームワーク、簡単なスクリプト・プログラミング言語、そして各種の開発標準を作っています。要するに、基礎的な部分をやっているのです。彼らはCなどの基礎言語を使って、簡単な開発ツールを作って、ほかの応用開発会社に売ったり、この業界を牽引して膨大な資金をもらって、まさに世界を変えているところです。基礎言語を牛耳るのは会社にとって如何に大事かがわかりましょう。
そして、個人の技術者についてはどうでしょうか?残酷ながらプログラマには年齢が大敵です。簡単な言語ほど、できる人が多くて、年を取ると競争に負けやすくなって、結局管理職など少ない職位に競うしかなくなります。ですが、Cは難しくて (ほとんどの実装は自分で0からする必要がある) そもそも完全に深くまで身につけるものが少なくて、年を取ると経験は積み重ねてより価値が高くなっていくものです。「難しい技術ほど、年を取るとより価値は高くなる」。なので、個人の技術者の発展としてもいいことしかないと私は思います。
<2>. C言語は、無機質なコンソールプログラムしか作れないでしょうか?
今のGUIプログラムの時代に、Cのような無機質なコンソールプログラムを作って何の意味があるかと思うかもしれませんが、実は、CもGUIプログラムを作れるし、しかも何のIDEを要りません。
下記の部分的なプログラムは、昔、僕の作ったWindowsのプログラムで、IDE一切使わずに (Visual Studioなど要りません)、Cの定番なコンパイラであるGCCだけで、生成できます。 (ここで必要なResourceファイルを張り付けていません、全ソースが見たい方がいらっしゃいましたら、私に言ってください)
test_main.c
#include <windows.h> #include <gdiplus.h> //#include <gdiplusimaging.h> #include "resource.h" #pragma comment(lib, "gdiplus.lib") Gdiplus::GdiplusStartupInput gdiSI; ULONG_PTR gdiToken; HWND hWnd; int state; static LRESULT CALLBACK windProc(HWND, UINT, WPARAM, LPARAM); extern "C" int WINAPI WinMain( HINSTANCE hCurInst, HINSTANCE hPrevInst, PSTR pCmdLine, int nCmdShow ) { BOOL ret; MSG msg; WNDCLASSEX wndClass; TCHAR szClassName[] = TEXT("Preprocess Demo"); Gdiplus::GdiplusStartup(&gdiToken, &gdiSI, nullptr); wndClass.cbSize = sizeof(WNDCLASSEX); wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = windProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hCurInst; wndClass.hIcon = (HICON)::LoadImage(nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); wndClass.hIconSm = (HICON)::LoadImage(nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); wndClass.hCursor = (HCURSOR)::LoadImage(nullptr, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wndClass.lpszMenuName = TEXT(MAKEINTRESOURCE(DEMO_MENU)); //TEXT("DEMO_MENU"); wndClass.lpszClassName = szClassName; if (!::RegisterClassEx(&wndClass)) { return FALSE; } hWnd = ::CreateWindow( szClassName, szClassName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hCurInst, nullptr ); if (!hWnd) { return FALSE; } ::ShowWindow(hWnd, nCmdShow); ::UpdateWindow(hWnd); while ((ret = ::GetMessage(&msg, nullptr, 0, 0)) != 0) { if (ret == -1) break; else { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } Gdiplus::GdiplusShutdown(gdiToken); return (int)msg.wParam; } static LRESULT CALLBACK windProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { const TCHAR *szFileName = TEXT("image.jpg"); static Gdiplus::Bitmap *pImage = nullptr; static Gdiplus::Bitmap *pImage2 = nullptr; static CLSID id; static UINT num = 0; static UINT size = 0; static Gdiplus::Rect mem_rect; static Gdiplus::BitmapData bData; static BYTE *pSrcData = nullptr; static BYTE *pDstData = nullptr; static UINT w, h, stride, dstStride, j; int x, y; //static Gdiplus::ImageCodecInfo *pImageCodecInfo = nullptr; PAINTSTRUCT ps; HDC hdc; static RECT cRect; switch (msg) { case WM_CREATE: { Gdiplus::ImageCodecInfo *pImageCodecInfo = nullptr; #ifndef UNICODE WCHAR szFileNameW[MAX_PATH]; ::MultiByteToWideChar(932, 0, szFileName, -1, szFileNameW, sizeof(szFileNameW) / sizeof(TCHAR)); pImage = Gdiplus::Bitmap::FromFile(szFileNameW); #else pImage = Gdiplus::Bitmap::FromFile(szFileName); #endif //pImage2 = pImage->Clone(); h = pImage->GetHeight(); w = pImage->GetWidth(); //stride = ((w + 3) / 4) * 4; // const Gdiplus::Rect mRect = Gdiplus::Rect(0, 0, w, h); /* mem_rect = 0; mem_rect.right = w; mem_rect.top = 0; mem_rect.bottom = h; */ /* Gdiplus::GetImageEncodersSize(&num, &size); pImageCodecInfo = new Gdiplus::ImageCodecInfo[size]; Gdiplus::GetImageEncoders(num, size, pImageCodecInfo); for (j = 0; j < num; ++j) { if (wcscmp(L"image/jpeg", pImageCodecInfo[j].MimeType) == 0) { id = pImageCodecInfo[j].Clsid; break; } } delete [] pImageCodecInfo; */ // pSrcData = (BYTE*)malloc(stride * h * sizeof(BYTE)); /* bData.Width = w; bData.Height = h; bData.Stride = stride; bData.PixelFormat = PixelFormat24bppRGB; bData.Scan0 = (void*)pSrcData; */ /* Gdiplus::Status st = pImage->LockBits(&mRect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bData); { TCHAR szText[100] = {0}; ::wsprintf(szText, "status=%d", st); ::MessageBox(NULL, szText, TEXT("Print Value"), MB_OK); } pSrcData = (BYTE*)(bData.Scan0); */ stride = ((w * 3 + 3) / 4) * 4; //dstStride = ((w + 3) / 4) * 4; dstStride = stride; pDstData = (BYTE*)malloc(dstStride * h * sizeof(BYTE)); state = 0; // Get Bits } break; case WM_LBUTTONDOWN: break; case WM_PAINT: { hdc = ::BeginPaint(hWnd, &ps); ::GetClientRect(hWnd, &cRect); Gdiplus::Graphics myGraphics(hdc); myGraphics.DrawImage(pImage, 0, 0, (cRect.right - cRect.left) / 2 - 10, (cRect.bottom - cRect.top)); if (pImage2) { myGraphics.DrawImage(pImage2, (cRect.right - cRect.left) / 2 + 10, 0, (cRect.right - cRect.left) / 2 - 10, (cRect.bottom - cRect.top)); } myGraphics.~Graphics(); ::EndPaint(hWnd, &ps); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_EXIT: ::SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_FILEOPEN: break; case IDM_GRAY: if (state == 0) { /* { TCHAR szText[100] = {0}; ::wsprintf(szText, "real format=%d, rgb=%d, argb=%d", pImage->GetPixelFormat(), PixelFormat24bppRGB, PixelFormat32bppARGB); ::MessageBox(NULL, szText, TEXT("Print Value"), MB_OK); } */ ::GetClientRect(hWnd, &cRect); cRect.left = (cRect.right - cRect.left) / 2 + 10; const Gdiplus::Rect mRect = Gdiplus::Rect(0, 0, w, h); Gdiplus::Status st = pImage->LockBits(&mRect, Gdiplus::ImageLockModeRead, pImage->GetPixelFormat(), &bData); pSrcData = (BYTE*)(bData.Scan0); for (int y = 0; y < bData.Height; ++y) { for (int x = 0; x < bData.Width; ++x) { int src_index = (x * 3) + (y * bData.Stride); //int dst_index = x + y * dstStride; int tmp = (int)(pSrcData[src_index+0] * 0.114 + pSrcData[src_index+1] * 0.587 + pSrcData[src_index+2] * 0.299 + 0.5); tmp = (tmp < 0) ? 0 : ((tmp > 255) ? 255 : tmp); //pDstData[dst_index] = tmp; pDstData[src_index+2] = pDstData[src_index+1] = pDstData[src_index+0] = tmp; /* if (x == 100 && y == 100) { TCHAR szText[100] = {0}; ::wsprintf(szText, "GRAY = %3d, R=%d, G=%d, B=%d", pDstData[dst_index], pSrcData[src_index+2], pSrcData[src_index+1], pSrcData[src_index]); ::MessageBox(NULL, szText, TEXT("Print Value"), MB_OK); } */ } } { Gdiplus::Bitmap image(w, h, dstStride, PixelFormat24bppRGB, (BYTE*)pDstData); // hdc = ::BeginPaint(hWnd, &ps); pImage2 = image.Clone(); image.~Image(); /* Gdiplus::Graphics myGraphics(hdc); myGraphics.DrawImage(pImage2, (ps.rcPaint.right - ps.rcPaint.left) / 2 + 10, 0, (ps.rcPaint.right - ps.rcPaint.left) / 2 - 10, (ps.rcPaint.bottom - ps.rcPaint.top)); myGraphics.~Graphics(); ::EndPaint(hWnd, &ps); */ } pImage->UnlockBits(&bData); state = 1; ::InvalidateRect(hWnd, &cRect, TRUE); //::InvalidateRect(hWnd, nullptr, TRUE); ::UpdateWindow(hWnd); } break; /* case IDM_CUT_RECTANGLE: break; */ } break; case WM_DESTROY: if (pImage) { pImage->~Image(); } if (pImage2) pImage2->~Image(); //if (pSrcData) free(pSrcData); if (pDstData) free(pDstData); ::PostQuitMessage(0); break; default: return (::DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
・必要な環境:gcc 【code blocksを検索して、mingwコンパイル環境(windows)をインストールする必要がある。】
・コンパイルコマンド:$ windres.exe -i resource.rc -o resource.o // resourceを生成する $ g++ -o test_main -O3 ./*.c resource.o -lgdi32 -lgdiplus -mwindows
これで、下記のようなWindowsプログラムができた:
これが、CとC compilerの力です。何の特定なIDEも依存せずに、メモ帳でCのソースを書いて、コマンドラインでコンパイル命令を打っただけで、こういったプログラムができました。このことで、1つのCプログラムでMulti PlatformにコンパイルしてMulti Platform化することが可能になります。(所謂JavaのMulti Platform機能)
<3>. Cは本当にJavaと同じくらいの速さなのでしょうか?
CはJavaとの実行効率はほとんど大した差はないと、たくさんの人が言っていますが、ここには一つの前提条件があります。すなわち、JavaはJVMで常に最適化された状態であるが、Cの最適化はプログラマ次第です。なので、みんなよく比較している両者は:常に最適化されたJavaと、全然最適化されてないCです。この両者はほとんど差がないってことになります。
例えば、上記のGCCのコンパイル命令では-O3を書いておりますが、これはGCCに最適化するよう命令したものです。これを指定しないと、プログラムは最適されません。また、各CPUには、SIMDコマンドがありますけど、もしプログラムにはSIMDで書いてないと、SIMDも使わないですし、pthreadライブラリを使ってMultiThread化しないと、MultiThread化もしません。これらの最適化する技を使うと、百倍千倍の速さになります。僕は実際にとあるpythonのプロジェクト開発中で、Cython + Cを使って (PythonからCのものを呼び出す技)、単純のPython版より10000倍以上(当時は約3万倍以上)の速さを引き出した経験があります。<4>. 総括
上記の紹介を通して、Cはとっても魅力的な言語だとは思いませんか?
無論、僕もまだまだ勉強中でCに関してはわからないものもたくさんありますので、みんなさんと一緒にCの魅力を掘り出すことができれば幸いです。今後の内容に関してですが、大体は下記のような内容でお話を進めていきたいです:
(1). Cのオブジェクト指向 (C的面向对象) の書き方
(2). CのMulti Thread化 (pthreadの使い方)
(3). CのSIMDプログラミング (Intel / ArmのCPUを例に)
【(2) + (3) + compilerの最適化の3者同時最適化でどれほど早くなれるかを期待しよう。原付車をフェラーリ車に改造!】
(4). CとJavaの交互 (JNI層の書き方)
(5). CとPythonの交互 (Cythonの書き方)
(6). CとSwift/Objective-Cの交互
(7). Cによるカメラ(Sensor)の直接操作の仕方【組込み】
(8). CのNetworkプログラミング
(9). Windows CのAPIの基本
(10). ほかの諸々