Pokazywanie postów oznaczonych etykietą clang. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą clang. Pokaż wszystkie posty

5 lutego 2020

[C++] problemy z rzutowaniem

Chociaż static_cast i dynamic_cast (i inne) w C++ miały być lepszą wersją "surowego" rzutowania znanego z języka C, nie rozwiązały wszystkich problemów. dynamic_cast można stosować tylko tam, gdzie mamy do czynienia z polimorfizmem, w dodatku angażując to RTTI (Run Time Type Information), które może zaważyć na czasie wykonania programu. static_cast z kolei, posiada niezdefiniowane zachowanie jeżeli próbujemy rzutować obiekt w dół hierarchii.

W przykładzie poniżej kompilator nie zaprotestuje, gdy zrzutujemy obiekt klasy Base na klasę Derived. Problem w tym, że obiekt base rezerwuje mniej pamięci (nie ma pola value_b) niż obiekt derived. W konsekwencji, pisane do pola value_b, będzie skutkowało pisaniem po pamięci.
#include <iostream>

using namespace std;

struct Base {
    void fun() { printf("Base method\n"); }
    int value_a;
};

struct Derived : public Base {
    void fun() { printf("Derived method\n"); }
    int value_b;
};


int main() {
    Base* base = new Base{};
    Derived* derived = static_cast<Derived*>(base); // no-error!

    derived->value_a = 1;
    derived->value_b = 2;       // pisanie po pamięci!

    cout << derived->value_a << endl;
    cout << derived->value_b << endl;
}
Tutaj dopisało nam szczęści, program wykonał się prawidłowo.
$ clang++ -std=c++17  main.cpp
$ ./a.out 
1
2
Sprawa wygląda inaczej, gdy dołączymy address sanitizer.
$ clang++ -std=c++17 -fsanitize=address main.cpp
$ ./a.out
==23018==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x0000004c63ba bp 0x7fff3301c9b0 sp 0x7fff3301c9a8
WRITE of size 4 at 0x602000000014 thread T0
...

Sanitizer potrafi wykryć tego typu problem, tylko gdy zaczynamy pisać po nie swojej pamięci. Naukowcy: Byoungyoung Lee, Chengyu Song, Taesoo Kim i Wenke Lee z Georgia Institute of Technology, stworzyli jeszcze inne narzędzie do detekcji tego typu problemów. Oparte na LLVM, dodaje do każdego static_cast tablicę z informacjami o hierarchii dziedziczenia (czyli coś w rodzaju tego co posiada dynamic_cast) i w zgrabny sposób informuje gdzie obiekt został stworzony i gdzie źle rzutowany, gdy tylko takie rzutowanie nastąpi. Za swoją pracę zostali nagrodzeni przez Facebooka fajną nagrodą pieniężną.

1 października 2015

kompilacja clang-a i libc++

Z kolejnymi wydaniami, pojawiają się delikatne różnice w procesie instalacji, warto więc śledzić poniższe linki.
Ściągnięcie źródeł + instalacja:
git clone http://llvm.org/git/llvm.git

cd llvm/tools
git clone http://llvm.org/git/clang.git

cd llvm/projects
git clone http://llvm.org/git/compiler-rt.git

cd llvm/projects
git clone http://llvm.org/git/libcxx.git
git clone http://llvm.org/git/libcxxabi.git

cd llvm/projects
git clone http://llvm.org/git/test-suite.git

mkdir llvm_build
mkdir llvm_root
cd llvm_build

cmake -G "Unix Makefiles" DCMAKE_INSTALL_PREFIX=/home/beru/llvm_root/ ../llvm
make -j2
make install
Kompilacja prostego Hello World. Dzięki temu zlepkowi argumentów jestem w stanie używać przekompilowanej przez siebie wersji biblioteki standardowej z przekompilowaną wersją clang-a jak i tą pochodzącą z repozytorium dystrybucji.
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/beru/llvm_root/lib/

~/llvm_root/bin/clang++ main.cpp -std=c++14 -stdlib=libc++ \
    -nodefaultlibs -lc++ -lc++abi -lm -lc -lgcc_s -lgcc \
    -I/home/beru/llvm_root/include/c++/v1/ \
    -L/home/beru/llvm_root/lib/

27 września 2015

[clang] Dynamiczna i statyczna analiza programów

Coś co chciałem wypróbować już bardzo dawno temu, a mianowicie narzędzie do statycznej i dynamicznej analizy kodu/programów. Zachęcił mnie do tego przede wszystkim wykład z GoginNative 2013: The Care and Feeding of C++’s Dragons (0:51h, 1:05h). Najwyraźniej funkcjonalność którą dostarcza kompilator (clang), została wydzielona do osobnego narzędzia jakim jest scan-build, choć nie eksperymentowałem z tym rozwiązaniem za dużo. Kilka przydatnych linków:

Clang Static Analyzer

Narzędzie korzysta ze zbioru reguł, które wciąż się rozrastają. Można skorzystać z domyślnych ustawień lub samodzielnie wskazać pod jakim kątem ma być przeskanowany kod. Przykład z dzieleniem przez zero, zaczerpnięty z dokumentacji.
#include <iostream>

using namespace std;

int test(int z) {
    if (z == 0)
        return 1 / z;
    return -1;
}

int main() {
    int i = 63;
    std::cin >> i;
    test(i);
    return 0;
}
W pierwszych dwóch linijkach dwa alternatywne wywołania:
clang++ -std=c++14 --analyze -Xanalyzer -analyzer-output=text main.cpp
# inna wersja
clang++ -std=c++14 --analyze -Xanalyzer -analyzer-checker=core.DivideZero -Xanalyzer -analyzer-output=text main.cpp
Wynik:
main.cpp:7:18: warning: Division by zero
        return 1 / z;
                 ^
main.cpp:14:10: note: Passing value via 1st parameter 'z'
    test(i);
         ^
main.cpp:14:5: note: Calling 'test'
    test(i);
    ^~~~~~~
main.cpp:6:9: note: Assuming 'z' is equal to 0
    if (z == 0)
        ^~~~~~
main.cpp:6:5: note: Taking true branch
    if (z == 0)
    ^
main.cpp:7:18: note: Division by zero
        return 1 / z;
               ~~^~~
1 warning generated.
Niestety przykłady które stworzyłem samodzielnie, już nie podają takich wyników. Testy przeprowadzałem na wersji 3.6.0 oraz 3.8.0. No nic, rozwiązanie i tak jest interesujące, liczę że w przyszłości narzędzie się rozwinie.
#include <iostream>

using namespace std;

int test2(int z) {
    return 1 / z;
}

int test3(int z) {
    if (z > 15)
        return 1;
    return 1 / z;
}

int main() {
    int i = 63;
    std::cin >> i;

    test2(i);
    test3(i);

    return 0;
}

Sanitizer

Innym narzędziem, które istnieje w clang-u jest sanitizer, a właściwie ich kolekcja. Analiza przeprowadzana jest dynamicznie, więc najlepsze efekty można uzyskać, jeżeli kod był tworzony razem z testami. Sprawdziłem jedynie AddressSanitizer, ale w dokumentacji można odnaleźć informacji o innych:
#include <iostream>

using namespace std;

int main() {
    char tab1[5];
    for(int i = 0; i <= 5; ++i)
        tab1[i] = 'x';
    return 0;
}

Wywołanie:
clang++ -std=c++14 -fsanitize=address main.cpp
./a.out
Rezultat:
=================================================================
==5748==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd47e300b5 at pc 0x0000004dc5ad bp 0x7ffd47e30010 sp 0x7ffd47e30008
WRITE of size 1 at 0x7ffd47e300b5 thread T0
    #0 0x4dc5ac  (/home/beru/synetizer_test/a.out+0x4dc5ac)
    #1 0x7fcefcca4a3f  (/lib/x86_64-linux-gnu/libc.so.6+0x20a3f)
    #2 0x4350e8  (/home/beru/synetizer_test/a.out+0x4350e8)

Address 0x7ffd47e300b5 is located in stack of thread T0 at offset 53 in frame
    #0 0x4dc32f  (/home/beru/synetizer_test/a.out+0x4dc32f)

  This frame has 3 object(s):
    [32, 36) ''
    [48, 53) 'tab1' <== Memory access at offset 53 overflows this variable
    [80, 84) 'i'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
Shadow bytes around the buggy address:
  0x100028fbdfc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdfd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdfe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100028fbe010: f1 f1 f1 f1 04 f2[05]f2 f2 f2 04 f3 00 00 00 00
  0x100028fbe020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==5748==ABORTING

25 grudnia 2014

clang-modernize

Ciekawe narzędzie przedstawione przez Chandlera Carrutha na GoingNative 2013:
Jego głównym zadaniem jest przerobienie istniejącego kodu, wprowadzając nowe mechanizmy ze standardu C++11. Nie jest tego wiele, ale pokazuje, jakie moce drzemią w samym clang-u. Testy przeprowadzałem na wersji 3.5
clang-modernize -add-override \
                -loop-convert \
                -pass-by-value \
                -replace-auto_ptr \
                -use-auto \
                -use-nullptr \
                cpp_modernize.cpp
Niestety nie udało mi się zmusić do działania opcji "pass-by-value", które ma pozwolić na zadziałanie move sementic.
#include <iostream>
#include <memory>
#include <vector>
#include <map>

class ICar {
public:
    virtual ~ICar() {};
    virtual int engine(const std::string& name) = 0;
};

class Car : public ICar {
public:
-   virtual int engine(const std::string& name) {
+   virtual int engine(const std::string& name) override {
        std::cout << "Engine " << name << " start" << std::endl;
        return 44;
    }
};

std::vector<std::string> rewrite(const std::map<std::string, int> m) {
    std::vector<std::string> out;
-   for (std::map<std::string, int>::const_iterator it = m.cbegin(); it != m.cend(); ++it) {
+   for (auto it = m.cbegin(); it != m.cend(); ++it) {
        out.push_back(it->first);
    }

    return out;
}

int main() {
    std::map<std::string, int> m = {{"3", 3}, {"4", 4}, {"8", 8}};
    std::vector<std::string> v = rewrite(m);
-   std::auto_ptr<ICar> ferrari(new Car());
+   std::unique_ptr<ICar> ferrari(new Car());

    int oil_counter = 0;
-   for (int i = 0; i < v.size(); ++i) {
-       if (ferrari.get() != NULL) {
-           int result = ferrari->engine(v[i]);
+   for (auto & elem : v) {
+       if (ferrari.get() != nullptr) {
+           int result = ferrari->engine(elem);
            oil_counter += result;
        }
    }
    return 0;
}

6 sierpnia 2014

clang-format

clang-format.py umożliwia zmianę formatowania pliku (C++) według przyjętej w danym projekcie konwencji. Pokrótce omówił to narzędzie Chandler Carruth w swoim wykładzie na GoingNative 2013 (25 minuta):
Potrafi on korzystać z kilku wbudowanych styli: Google, WebKit, llvm. Możliwe jest także skorzystanie z własnego, za pomocą przełącznika -style=file. Styl taki zostanie zaczytany z pliku .clang-format, który musi znajdować się gdzieś w ścieżce powyżej wywołania. Własny styl można zbudować, na podstawie już istniejącego, najpierw zrzucając oryginał (--dump-config) go pliku, a następnie modyfikując.
# użyj wbudowanego stylu
clang-format-3.5 -style=WebKit -i main.cpp

# zrzuć styl Google do pliku .clang-format
clang-format-3.5 -style=Google --dump-config > .clang-format

# skorzystaj z własnego stylu
clang-format-3.5 -style=file -i main.cpp
Działanie formatera, można zautomatyzować np. w QtCreatorze. Jedna uwaga, wszelkie modyfikacje w:
"Tools | Options... | Environment | External Tools | Add | Add Tool", kasują skrót klawiaturowy, który został podpięty dla takiego formatera.

21 grudnia 2013

[C++14] Hello world

Kompilacja z ficzerami dla C++14. Niestety dla clang-3.4 wymagało to trochę zachodu, ale w końcu się udało. Przed kompilacją trzeba było doinstalować "libc++-dev - LLVM C++ Standard library (development files)".
sudo apt-cache search libc
sudo apt-get install libc++-dev
Następnym krokiem była edycja pliku cstdio. Jak przeczytałem pod tym linkiem, ::gets najwyraźniej zostało usunięte ze standardu, a ja przy próbie kompilacje otrzymywałem następujący error:
/usr/include/c++/v1/cstdio:156:9: error: no member named 'gets' in the global namespace
using ::gets;
      ~~^
1 error generated.
Sama poprawka jaką należy wprowadzić do /usr/include/c++/v1/cstdio, czyli otoczyć gets defajnsem:
#if !_ISOC11_SOURCE
using ::gets;
#endif
Kompilacja wygląda następująco:
$ clang++ -std=c++1y -stdlib=libc++ main.cpp
I kod, na którym testowałem
#include <iostream>

using namespace std;

auto fun() {
    return "Hello world";
}

int main() {
    cout << fun() << endl;
    return 0;
}

12 listopada 2013

include-what-you-use, podejście drugie

W sierpniu include-what-you-use doczekał się aktualizacji, a ja postanowiłem podjąć kolejną próbę kompilacji tego narzędzia. Wszystko wykonałem jak za ostatnim razem. Tym razem zaczęło działać! Dodatkowo zapisałem zmiany w llvm/tools/clang/tools/CMakeLists.txt ...
add_subdirectory(diagtool)
add_subdirectory(driver)
add_subdirectory(include-what-you-use)
if(CLANG_ENABLE_REWRITER)
  add_subdirectory(clang-format)
endif()

if(CLANG_ENABLE_ARCMT)
  add_subdirectory(libclang)
  add_subdirectory(c-index-test)
  add_subdirectory(arcmt-test)
  add_subdirectory(c-arcmt-test)
endif()
if(CLANG_ENABLE_STATIC_ANALYZER)
  add_subdirectory(clang-check)
endif()

# We support checking out the clang-tools-extra repository into the 'extra' ...
add_llvm_external_project(clang-tools-extra extra)
... oraz llvm/tools/clang/tools/Makefile
CLANG_LEVEL := ..

include $(CLANG_LEVEL)/../../Makefile.config

DIRS := include-what-you-use
PARALLEL_DIRS := driver diagtool

ifeq ($(ENABLE_CLANG_REWRITER),1)
  PARALLEL_DIRS += clang-format
endif

ifeq ($(ENABLE_CLANG_STATIC_ANALYZER), 1)
  PARALLEL_DIRS += clang-check
endif

ifeq ($(ENABLE_CLANG_ARCMT), 1)
  DIRS += libclang c-index-test c-arcmt-test
  PARALLEL_DIRS += arcmt-test
endif

# Recurse into the extra repository of tools if present.
OPTIONAL_PARALLEL_DIRS := extra

ifeq ($(BUILD_CLANG_ONLY),YES)
  DIRS := libclang c-index-test include-what-you-use
  PARALLEL_DIRS := driver
  OPTIONAL_PARALLEL_DIRS :=
endif

include $(CLANG_LEVEL)/Makefile
Poniżej plik, z którym eksperymentowałem.
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <boost/optional.hpp>

using namespace std;

int main()
{
    cout << "Hello World!" << endl;
    return 0;
}
Niestety nie udało mi się zmusić CMake, aby wygenerował dla mnie odpowiedni plik Makefile z ustawionym include-what-you-use jako kompilator.
Najpierw próbowałem ustawić CMAKE_CXX_COMPILER w CMakeLists.txt (set dla CMAKE_CXX_COMPILER musi znaleźć się przed project!). Trzeba było też wyczyścić cały projekt (został kod i CMakeLists.txt) i zrestartować QtCreator-a, żeby pojawił się jakiś efekt. CMake poskarżył się tylko, że tym się nie da kompilować.
set(CMAKE_CXX_COMPILER "~/poligon/build/Debug+Asserts/bin/include-what-you-use")
project(include_test)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
Drugie podejście z linii komend, efekt niestety taki sam.
cmake CMakeLists.txt \
  -DCMAKE_CXX_COMPILER="~/poligon/build/Debug+Asserts/bin/include-what-you-use"
Skończyło się na własnej wersji Makefile.
make -k CXX=~/poligon/build/Debug+Asserts/bin/include-what-you-use
A oto wynik.
main.cpp should add these lines:

main.cpp should remove these lines:
- #include <stdio.h>  // lines 3-3
- #include <boost/optional.hpp>  // lines 4-4
- #include <cstring>  // lines 2-2

The full include-list for main.cpp:
#include <iostream>                     // for operator<<, basic_ostream, etc
---
make: *** [main] Error 1

3 listopada 2013

[C++11] Współbieżność

Pierwsze podejście do współbieżności, wprowadzonej do biblioteki standardowej wraz z nowym standardem.
#include <thread>
#include <iostream>

void hello()
{
    std::cout << "Hello World!" << std::endl;
}

int main()
{
    std::thread t(hello);
    t.join();
    return 0;
}
Było trochę kłopotów przy próbie kompilacji (gcc - 4.7.3, clang - 3.2-1). gcc potrzebuje dodatkowej flagi w postaci pthread, natomiast clang w wersji 3.2 jeszcze nie radzi sobie z wątkami, ale i na to znalazł się sposób.
Budowanie z linii poleceń:
$ g++ -pthread --std=c++11 main.cpp
$ clang++ -pthread --std=c++11 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 \
     -D__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 main.cpp
Poniżej ustawienie dla CMakeList.txt (cmake):
project(thread_hello)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")

27 października 2013

[C++Now] Interactive, Introspected C++ at CERN

Fajny wykład (z konferencji C++Now 2013) na temat pracy w CERN, oraz narzędzie/oprogramowania jakie się tam wytwarza: iterpreter C++ (projekt cling), oraz bibliotekę ROOT na potrzeby fizyków.

5 października 2013

[C++] Debugowanie metaprogramów - Templight 2.0

Na tegorocznej (2013) konferencji C++Now, pojawił się bardzo interesujący wykład na temat debugowania/profilowania metaprogramów z wykorzystaniem Templight 2.0 (patch-a na kompilator clang).
Strona projektu:
Nakładania patch-a jest proste, aczkolwiek kompilacja trochę trwała. Na samym początku miałem problem związany z błędem podczas linkowania clang-a. Rozwiązaniem okazało się zwiększenie pamięci do 2GB dla mojej wirtualnej maszyny (VritulBox). Poniżej wszystkie kroki (skopiowane ze strony projektu).
svn co http://llvm.org/svn/llvm-project/llvm/branches/release_32 llvm

cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/branches/release_32 clang
cd ../..

cd llvm/tools/clang/tools
svn co http://llvm.org/svn/llvm-project/clang-tools-extra/branches/release_32 extra
cd ../../../..

cd llvm/projects
svn co http://llvm.org/svn/llvm-project/compiler-rt/branches/release_32 compiler-rt
cd ../..

cd llvm/tools/clang
patch -p0 -i ~/templight.diff
cd ../../..

mkdir build (for building without polluting the source dir)
cd build
../llvm/configure
make
Aby skompilować narzędzie (wykorzystuje Qt oraz graphviz) trzeba było doinstalować kilka paczek:
sudo apt-get install graphviz
sudo apt-get install libgraphviz-dev
sudo apt-get install qt-sdk

# Ścieżki do bibliotek
export QTDIR=/usr/share/qt4
export MANPATH=$QTDIR/doc/man:$MANPATH
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
PATH=$QTDIR/bin:$PATH

# Budowanie
cd ProfileDataViewer
qmake
make
Jeżeli w programie korzystamy z szablonów np. dostarczonych przez bibliotekę standardową wyników może być całkiem sporo (może być tu przydatny breakpoint - program Templar). Na początku pojawił się także problem z brakiem widoczności "bits/c++config.h" przez mojego clang-a (ponieważ korzystałem z iostream). Rozwiązaniem było dodanie dodatkowej flagi dla kompilatora:
Proces kompilacji:
# Problem
~/poligon/build/Debug+Asserts/bin/clang++ -templight main.cpp
/usr/lib/gcc/i686-linux-gnu/4.7/../../../../include/c++/4.7/iostream:38:10: fatal error: 'bits/c++config.h' file not found

# Fix
~/poligon/build/Debug+Asserts/bin/clang++ -I/usr/include/i386-linux-gnu/c++/4.7/ -templight main.cpp
Kompilator wygeneruje dla nas plik xml, którym należy nakarmić jeden z programów go analizujących np. Templar (debugger). Przykład, prosty program na obliczanie silni - 5!.
template <int N>
struct factorial
{
    enum { value = N * factorial<N-1>::value };
};

template <>
struct factorial<1>
{
    enum { value = 1 };
};

template <>
struct factorial<0>
{
    enum { value = 1 };
};

int main()
{
    factorial<5>::value;
    return 0;
}
A poniżej graf wygenerowany podczas analizy. Break na factorial<3>::value.

5 września 2013

[C++11] std::shared_ptr vs std::make_shared

Od wielu osób, słyszałem o wyższości stosowania dedykowanych metod w celu tworzenia inteligentnych wskaźników. Nadszedł czas przyjrzeć się tym dwóm mechanizmom bliżej, jak zawsze na bazie własnych testów, by ze świadomością móc korzystać z ich dobrodziejstw.

Swoje przemyślenia oparłem na "C++ Primer" oraz na artykule Herba Shuttera:

Kilka punktów, które mogą okazać się pomocne w przyszłości.
  • std::unique_ptr powinien być zawsze preferowany przed std::shared_ptr
  • std::shared_ptr i std::unique_ptr powinno być wybierane, tylko gdy chcemy skorzystać z własnego deletera (std::make_shared tego nie umożliwia), albo gdy adaptujemy stary kod i chcemy zarządzać surowym wskaźnikiem.
  • W innych przypadkach powinno się korzystać z std::make_unique i std::make_shared.
Zalety:
  • Upraszcza to kod. W jednej instrukcji zawarte jest tworzenie inteligentnego wskaźnika i chowany jest new (std::shared_ptr + new), który może rzucać trudne do wykrycia wyjątki. std::make_shared nas przed tym chroni (w C++17 został zmieniony sposób ewaluacji argumentów funkcji i nie jest to już problemem).
  • std::make_shared daje istotne optymalizacyjne usprawnienia. std::shared_ptr jedynie tworzy wskaźnik na zasób którym ma zarządzać (który zostanie stworzony przez new) i nie wie, czy to co mu podlega zostało stworzone specjalnie dla niego, czy ma do czynienia z surowym wskaźnikiem. Zasób taki będzie najprawdopodobniej w innym bloku pamięci + kompilator jak zawsze doda kilka ekstra bajtów, przy alokowaniu.
Wady:
  • Nie testowałem, aczkolwiek ma to sens. Ponieważ std::make_shared stworzy obiekt, a także reference counters w jednym bloku pamięci, pamięć taka będzie zwolniona dopiero, gdy nie będzie już żadnych std::weak_ptr wskazujących na ten obiekt. Tworzenie obiektu za pomocą std::shared_ptr ma tu taką przewagę, że będzie mógł on zwolnić zasób, którym zarządza i pozostawić przy życiu jedynie reference counters dla std::weak_ptr. Jeżeli zarządzany obiekt będzie duży, może się to odbić na wydajności.
std::make_shared zatroszczy się o to by wszystko zostało stworzone po kolei (ładnie to ilustrują obrazki zamieszczone przez Herba Shutter na jego stronie).
Dla dużej ilości małych obiektów, może to istotne przyśpieszyć działanie programu, ponieważ zmniejsza się czas dostępu do cache procesora. Właśnie to chciałem przetestować w swoim teście.
#include <iostream>
#include <memory>
#include <boost/date_time/posix_time/posix_time.hpp>

using namespace boost::posix_time;

struct MyObject {
    MyObject(std::string n): name(n) { }
    std::string name;
};

void fun(const std::shared_ptr<MyObject> &sp) {
    if (sp->name == "xxx")
        std::cout << "xxx" << std::endl;
}

int main() {
    const ptime time_start = microsec_clock::local_time();

    for (int i = 0; i < 100000000; ++i) {
#ifdef TEST_SHARED_PTR
        fun(std::shared_ptr<MyObject>(new MyObject("shared")));
#else
        fun(std::make_shared<MyObject>("make_shared"));
#endif
    }

    const ptime time_stop = microsec_clock::local_time();
    std::cout << "Time: " << time_start - time_stop << std::endl;

    return 0;
}
Kompilacja.
$ g++ main.cpp -std=c++11 -O2 -DTEST_SHARED_PTR
Poniżej zestawienie wyników dla gcc i clang-a. Zrobiłem też testy bez użycia flag optymalizacyjnych (-O2) i co ciekawe wyniki były zupełnie odwrotne! Nie wynikałem już w przyczynę tego stanu rzeczy. Może kiedyś rozwikłam tą zagadkę.

1 maja 2013

Delegating constructors

Delegating constructors używają innych konstruktorów z klasy do wykonania inicjalizacji, dzięki temu nie trzeba już korzystać z metod pomocniczych, by uwspólnić powtarzające się części kodu.
#include <iostream>

class Value {
public:
    Value(int i) {
        std::cout << "Value " << i << std::endl;
    }
};

class MyClass {
public:
    MyClass() : MyClass(4) {
        std::cout << "Constructor 1" << std::endl;
    }
    MyClass(int v) : MyClass("MyClass", v, 5) {
        std::cout << "Constructor 2" << std::endl;
        value3 = Value(77);
    }
    MyClass(std::string s, int v1, int v2) : value1(v1), value2(v2), value3(66) {
        std::cout << "Constructor 3" << std::endl;
        value3 = Value(88);
    }
private:
    Value value1;
    Value value2;
    Value value3;
};

int main()
{
    MyClass m;
    return 0;
}
Wyniki:
Value 4
Value 5
Value 66
Constructor 3
Value 88
Constructor 2
Value 77
Constructor 1
Wygląda na to, że na liście konstruktora, który oddelegowuje swoje działanie może się pojawić tylko i wyłącznie inny konstruktor (żadne zmienne) - wywnioskowane na podstawie informacja z clang-a.
$ clang++ main.cpp -std=c++11
main.cpp:12:17: error: an initializer for a delegating constructor must appear alone
    MyClass() : value3(11), MyClass("MyClass", 4, 5) {
                ^~~~~~~~~~
1 error generated.

29 grudnia 2012

include-what-you-use, pierwsze podejście

Na GoingNative 2012, Chandler Carruth wspominał o narzędziu (korzystającego z API clang-a), które ułatwiało by zarządzanie plikami nagłówkowymi, sugerując przeniesienie ich deklaracja do miejsc, gdzie są naprawdę potrzebne, co istotnie miało przyśpieszyć czas kompilacji. Nie udało mi się uzyskać informacji, gdzie to narzędzie można znaleźć. Pocztą pantoflową, dowiedziałem się o include-what-you-use.

Dodatkowe, przydatne linki:
http://clang.llvm.org/get_started.html
http://code.google.com/p/include-what-you-use/wiki/InstructionsForUsers

Niestety pierwsza, próba zakończyła się dla mnie porażką. Korzystając z wersji "trunk" obu projektów, kompilacja nie powiodła się. Postanowiłem więc skorzystać z odrobinę starszych wersji (RELEASE_30/final), dla obu projektów. Wydawało mi się, że tak zsynchronizowane projekty, już razem zadziałają. Miałem rację, przynajmniej jeśli chodzi o kompilację. Poniżej, sekwencja poleceń, która mi to zapewniła:
svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_30/final llvm

cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_30/final clang
cd ../..

cd llvm/projects
svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_30/final/projects compiler-rt
cd ../..

cd llvm/tools/clang/tools
svn co http://include-what-you-use.googlecode.com/svn/tags/clang_3.0/ include-what-you-use
cd ..
# Wyedytować tools/clang/tools/Makefile i dodać include-what-you-use do zmiennej DIRS
# Wyedytować tools/clang/tools/CMakeLists.txt i dodać add_subdirectory(include-what-you-use)
cd ../../..

# Aby nie zaśmiecać, tworzymy katalog specjalnie na build (nie będzie make install)
mkdir build
cd build
../llvm/configure
make
Niestety, dla mojego testowego projektu, działanie programu kończy się błędem (co jest oczekiwane - nie wiem, tylko czy to ma być taki błąd), i nie generuje raportu.
make -k CXX=~/build/Debug/bin/include-what-you-use > log
Ku pamięci, mam nadzieje, że wrócę jeszcze kiedyś do tematu.

23 grudnia 2012

Prosty Makefile

Musiałem zbudować, bardzo mały plik Makefile. Tutaj przydatny tutorial:
http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/

Make bez argumentów uruchomi, pierwszą napotkaną regułę, w tym przypadku będzie to main. Po dwukropku możemy podać listę (oddzieloną spacjami) zależność np. nazwy reguł na zbudowanie plików *.o, zanim, będziemy z nich korzystać podczas linkowania. Komendy, które składają się na daną regułę muszą być poprzedzone tabulatorem!
CXX=g++

main:
        $(CXX) -o main main.cc Lion.cc Lungs.cc -I.

Istnieje możliwość przekazania parametrów, do skryptu, w poniższym przykładzie, zmieniamy kompilator z domyślnego gcc, na clang-a.
$ make CXX=clang++
clang++ -o main main.cc Lion.cc Lungs.cc -I.