Cplus2 Linux Development Course Note

来自牛客 C++ 项目.

Ch2. 多进程开发

2.1 进程概述

  • 程序是包含一系列信息的文件, 这些信息描述了如何在运行时创建一个进程.
  • 进程是正在运行的程序的实例, 是一个具有一定独立功能的程序关于某个数据集合的一次运行活动, 是操作系统动态执行的基本单元. 在传统的操作系统中, 进程既是基本的分配单元, 也是基本的执行单元.
  • 进程是内核定义的抽象实体, 并为该实体分配用以执行程序的各项系统资源. 从内核角度看, 进程由用户内存空间一系列内核数据结构组成.
  • 单道程序, 多道程序, 时间片, Linux 进程调度算法.
  • 并行: 指在同一时刻,有多条指令在多个处理器上同时执行. 并发: 指在同一时刻只能有一条指令执行, 但多个进程指令被快速地轮换执行, 使得宏观上具有多个进程同时执行的效果.
  • 内核会为每个进程分配一个进程控制块 (Processing Control Block, PCB), 维护进程相关的信息. Linux 下的 PCB 是 task_struct 结构体, 可在 /usr/src/linux-headers-xxx/include/linux/sched.h 文件中查询相关定义. 其成员包括不限于: 进程 id, 进程状态, 进程切换时需要保存和恢复的 CPU 寄存器, 描述虚拟地址空间的信息, 描述控制终端的信息, 当前工作目录, umask 掩码, 文件描述符表, 和信号相关的信息, 用户 id 与组 id, 会话和进程组, 进程可用资源上限 (ulimit -a) 等.

2.2 进程状态转换

  • 三态模型. 就绪: 进程具备运行条件, 等待系统分配处理器以便运行; 运行: 进程占有处理器正在运行; 阻塞: wait 或 sleep, 进程不具备运行条件, 正在等待某个事件的完成. 五态模型. 新建: 进程刚被创建时的状态, 尚未进入就绪队列; 就绪; 运行; 阻塞; 终止: 正常结束或异常终止, 或被操作系统或其他进程终止. 终止态进程不再执行, 待其他进程完成了对其的信息抽取后, 操作系统将删除该进程.
  • 查看进程: | ps aux / ajx | | --------------------------------------------- | | a: 显示终端上的所有进程, 包括其他用户的进程 | | u: 详细信息 | | x: 没有控制终端的进程 | | j: 与作业控制相关的信息 | tty: 终端信息; STAT: 状态信息; PPID, PID, PGID: 父进程, 进程, 进程组 id. ## 2.3 进程创建

Chapter 1. Getting Started

  • The operating system runs a C++ program by calling main.

  • On most systems, the value returned from main is a status indicator. A return value of 0 indicates suceess. A nonzero return has a meaning that is defined by the system. Ordinarily a nonzero return indicates what kind of error occurred.

  • A type defines both the contents of a data element and the operations that are possible on those data.

  • The value returned from main is accessed in a system-dependent manner. On both UNIX and Windows systems, after executing the program, you must issue an appropriate echo command.
    On UNIX systems, we obtain the status by writing

    1
    $ echo $?
    To see the status on a Windows system, we write
    1
    $ echo %ERRORLEVEL%

  • The output operator << takes two operands: the left-hand operand must be an ostream object; the right-hand operand is a vaule to print. The result of the output operator is its left-hand operand. The input operator >> behaves analogously to the output operator.

  • The manipulator endl has the effect of ending the current line and flushing the buffer associated with that device. Flushing buffer ensures that all the output the program has generated so far is actually written to the output stream, rather than sitting in memory waiting to be written. By default, reading cin flushes cout; cout is also flushed when the program ends normally; writes to cerr are not buffered, usually used for error messages or other output that is not part of the normal logic of the program.
    Programmers often add print statements during debugging. Such statements should always flush the stream. Otherwise, if the program crashes, output my be left in the buffer, leading to incorrect inferences about where the program crashed.

  • When a comment pair does span multiple lines (surely, the best way is to use single-line comments cause comment pairs do not nest), it is often a good idea to indicate visually that the inner lines are part of a multiple comment (e.g. begin each line with an asterisk).

  • The best way to comment a block of code is to insert single-line comments at the beginning of each line in the section we want to ignore since that code might contain nested comment pairs but comment pairs do not nest.

  • The variable defined in init-statement of for's header exists only inside the for; it is not possible to use the variable after the loop terminates.

  • When we use an istream as a condition,

    1
    while (std::cin >> value)
    the effect is to test the state of the stream. If the stream is valid—that is, if the stream hasn't encountered an error—then the test succeeds.
    An istream becomes invalid when we hit end-of-file (e.g. ctrl+z on Win or ctrl+d on UNIX) or encounter an invalid input, such as reading a value that is not an integer.

  • Most operating systems support file redirection (to avoid tediously repeated typing), which lets us associate a named file with the standard input and the standard output:

    1
    $ exefile <infile >outfile
    This command will read input from a file named infile and write its output to a file named outfile in the current directory.

Chapter 2. Variables and Basic Types

  • C++ is a statically typed language; type checking is done at compile time.

  • To give meaning to memory at a given address, we must know the type of the value stored there. The type determines how many bits are used and how to interpret those bits.

  • Which of the two character representations signed char and unsigned char is equivalent to char depends on the compiler. So computations using char are especially problematic because char is signed on some machines and unsigned on others. If you need a tiny integer, explicitly specify either signed char or unsigned char.
    Do not use plain char or bool in arithmetic expressions. Use them only to hold characters or truth values.

  • If we assign an out-of-range value to an object of unsigned type, the result is the remainder of the value modulo the number of values the target type can hold. If we assign an out-of-range value to an object of signed type, the result is undefined. Expressions that mix signed and unsigned values can yield suprising results when the signed value is negative since signed values are automatically converted to unsigned in an arithmetic expression.

  • Programs usually should avoid implementation-defined behavior. Such programs are said to be nonportable. When program is moved to another machine, code that relied on implementation-defined behavior may fail.
    Tracking down these sorts of problems in previously working programs is, mildly put, unpleasant.

  • The type of a string literal is array of constant chars. The compiler appends a null character ('\0') to every string literal. Thus, the actual size of a string literal is one more that its apparent size.

  • Two string literals that appear adjacent to one another and that are seperated only by spaces, tabs or newlines are concatenated into a single literal.
    We use this form of literal when we need to write a literal that would otherwise be too large to fit comfortably on a single line.

  • Initialization is not assignment. Initialization happens when a variable is given a value when it is created. Assignment obliterates an object's current value and replaces that value with a new one.

  • When used with variables of built-in type, the list initialization has one important property: The compiler will not let us initialize variables of built-in type if the initializer might lead to the loss of information:

    1
    2
    3
    long double ld = 3.1415926536;
    int a{ld}, b = {ld}; // error: narrowing conversion required
    int c(ld), d = ld; // ok: but value will be truncated

  • The value of an object of built-in type that is not explicitly initialized depends on where it is defined. Variables defined outside any function body are initialized to zero. With one exception, variables of built-in type defined inside a function are uninitialized. The value of an uninitialized variable of built-in type is undefined. (NCC)

  • DEFINITION \(\subseteq\) DECLARATION.
    To support seperate compilation, C++ distinguishes between declarations and definitions. A declaration makes a name known to the program. A file that wants to use a name defined elsewhere includes a declaration for that name.
    Variables must be defined exactly once but can be declared many times. To use the same variable in multiple files, we must define that variable in one—and only one—file. Other files that use that variable must declare—but not define—that variable. It is an error to provide an initializer on an extern inside a function.

  • Local object with a same as the global object will hide the global one. Use scope operator :: to override the default scoping rules. When the scope operator has an empty left-hand side, it is a request to fetch the name on the right-hand side from the global scope.

  • Ordinarily, when we initialize a variable, the value of the initializer is copied into the object we are creating. When we define a reference, instead of copying the initializer's value, we bind the reference to its initializer. References must be initialized and cannot be rebound to refer to other different objects.
    A reference is not an object. Instead, it is just another name for an already existing object. So we may not define a reference to a reference. Since references do not have addresses, we may not define a reference or pointer to a reference.

  • nullptr is a literal that has a special type that can be converted to any other pointer type. If the pointer is 0, then the condition is false. Any nonzero pointer evaluates as true. Using an invalid pointer as a condition or in a comparison is undefined.

  • A void* pointer holds an address, but the type of the object at that address is unknown. We cannot use a void* to operate on the object it addresses—we don't know that object's type. Generally, we use a void* pointer to deal with memory as memory, rather than using the pointer to access the object stored in that memory.

  • In the code

    1
    int i = 1024, *p = &i, &r = i;
    the int is called base type, * and & are type modifiers.

  • It can be easier to understand complicated pointer or reference declarations if you read them from right to left.

    1
    2
    3
    int *p;
    int *&r = p; // r is a reference to the pointer p
    int* ip, &r = ip; // invalid bind

  • const variables are defined as local to the file. When we define a const with the same name in multiple files, it is as if we had written definitions for seperate variables in each file. When we have a const that we want to share across multiple files but whose initializer is not a constant expression, we need to use extern on both its definition and declaration(s).

  • We can initialize a reference to const from any expression that can be converted to the type of the reference. In particular, we can bind a reference to const to a nonconst object, a literal, or a more general expression.

    1
    2
    3
    4
    int i = 42;
    const int &r1 = i;
    const int &r2 = 42;
    const int &r3 = r1 * 2;
    but here we cannot use r1 to change i. When we use
    1
    2
    double dval = 3.14;
    const int &ri = dval;
    the compiler transforms this into something like
    1
    2
    const int temp = dval;
    const int &ri = temp;
    Since binding a reference to a temporary is almost surely not what the programmer intended and the language makes it illegal, we cannot use a nonconst reference in the above examples.

  • We use the term top-level const to indicate that the pointer itself is a const. When a pointer can point to a const object, we refer to that const as a low-level const. The distinction between top-level and low-level matters when we copy an object: top-level consts are ignored at that time. On the other hand, low-level const is never ignored. When we copy an object, both objects must have the same low-level const qualification or there must be a conversion between the types of the two objects.
    const in reference types is always low-level.

  • When define a pointer in a constexpr declaration, the constexpr specifier applies to the pointer, not the type to which the pointer points, which means that the constexpr imposes a top-level const on the objects it defines.

  • Attension,

    1
    2
    typedef char *pstring;
    const pstring *cstr = 0; // cstr is a constant pointer to char, not a pointer to `const` `char`, since the type of pstring is "pointer to `char`", which means that the base type of declaration is a pointer type

  • As with any other type specifier, we can define multiple variables using auto. Because a declaration can involve only a single base type, the initializers for all the variables in the declaration must have types that are consistent with each other.

  • auto ordinarily ignores top-level consts. As usual in initializations, low-level consts, such as when an initializer is a pointer to const, are kept. If we want the deduced type to have top-level const, we must say so explicitly.

    1
    2
    3
    4
    5
    6
    const int ci = 42;
    auto a = ci; // a is an int
    const auto b = ci; // b has type const int
    auto &c = ci; // c is a const int&
    auto &d = 42; // error: cannot bind a plain reference to a literal
    const auto &e = 42; // ok
    From above we can see that when we ask for a reference to an auto-deduced type, top-level consts in the initializer are not ignored.

  • When the expression to which we apply decltype is a variable, decltype returns the type of that variable, including top-level const and references. And decltype returns a reference type for expressions that yield objects that can stand on the left-hand side of the assignment.

    1
    2
    3
    4
    5
    6
    const int ci = 0, &cj = ci;
    decltype(ci) a = 0; // a has type const int
    decltype(cj) b; // error: b is a reference and must be initialized
    int i = 42, *p = &i, &r = i;
    decltype(r + 0) c; // ok: addition yields an int; c is an (uninitialized) int
    decltype(*p) d; // error: d is int& and must be initialized
    If we use decltype(r), we will get a reference type. Another important difference between decltype and auto is that the deduction done by decltype depends on the form of its given expression.
    1
    2
    decltype((i)) e; // error: e is int&
    decltype(i) f; // ok: f is an int

Chapter 3. Strings, Vectors, and Arrays

  • Code inside headers ordinarily should not use using declarations. The reason is that the contents of a header are copied into the including program's text. If a header has a using declaration, then every program that includes that header gets that same using declaration. As a result, a program that didn't intend to use the specified library name might encounter unexpected name conflicts. Use the C++ versions of C library headers.

  • Like the input operator, getline returns its istream argument. The newline that causes getline to return is discarded; the newline is not stored in the string.

  • String literals are not standard library strings. The string library lets us convert both character literals and character string literals to strings. E.g. when we mix strings and string or character literals, at least one operand to each + operator must be of string type.

  • When we use curly braces, we're saying that, if possible, we want to list initialize the object. That is, if there is a way to use the values inside the curly braces as a list of element initializers, the class will do so. Only if it is not possible to list initialize the object will the other ways to initialize the object be considered.

  • Since vectors grow efficiently, it is often unnecessary—and can result in poorer performance—to define a vector of a specific size.
    The exception to this rule is if all the elements actually need the same value. If differing element values are needed, it is usually more efficient to define an empty vector and add elements as the values we need become known at run time.

  • The body of a range for must not change the size of the sequence over which it is iterating. (NCC)

  • A valid iterator either denotes an element or denotes a position one past the last element in a container. All other iterator values are invalid. If container is empty, begin returns the same iterator as the one returned by end-they are both off-the-end iterators.

  • Any operation, such as push_back, that changes the size of a vector potentially invalidates all iterators into that vector. It is important to realize that loops that use iterators should not add elements to the container to which the iterators refer.

  • Iterators are equal if they denote the same element or if they are both off-the-end iterators for the same container. Otherwise, they are unequal. Subtracting two iterators yields the number that when added to the right-hand iterator yields the left-hand iterator. The iterators must denote elements in, or one past the end of, the same container.
    In subtracting the result type is a signed integral type named difference_type.

  • In most expressions, when we use an object of array type, we are really using a pointer to the first element in that array.

  • Unlike subscripts for vector and string, the index of the built-in subscript operator is not an unsigned type.

Chapter 4. Expressions

  • Roughly speaking, when we use an object as an rvalue, we use the object's value (its contents). When we use an object as an lvalue, we use the object's identity (its location in memory). We can use an lvalue when an rvalue is requried, but we cannot use an rvalue when an lvalue (i.e., a location) is required (with one exception that will be covered later).

  • sizeof does not evaluate its operand, so dereferencing an invalid pointer as the operand to sizeof is safe because the pointer is not actually used.

Chapter 5. Statements

  • Throwing an exception terminates the current function and transfers control to a handler that will know how to handle this error. When a catch is selected to handle an exception, the associated block is executed.

Chapter 6. Functions

  • Parameter initialization works the same way as variable in initialization.

  • Using reference parameters could avoid copying objects of large class types or large containers. (Moreover, some class type cannot be copied.) Reference parameters that are not changed inside a function should be references to const.

  • When you use the arguments in argv, remember that the optional arguments begin in argv[1], arg[0] contains the program's name (or empty string), not user input.

  • Never return a reference or pointer to a local object. Reference returns are LVALUES.

  • The form of a function that returns a pointer to an array is

    1
    Type (*function(parameter_list))[dimension]

  • Overloaded functions must differ in the number or the type(s) of their parameters. It is an error for two functions to differ only in terms of their return types. A parameter that has a top-level const is indistinguishable from one without a top-level const. On the other hand, we can overload based on whether the parameter is a reference (or pointer) to the const or nonconst version of a given type; such consts are low-level.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Record lookup(Phone);
    Record lookup(const Phone); // redeclares

    Record lookup(Phone*);
    Record lookup(Phone* const); // also redeclares

    Record lookup(Account&);
    Record lookup(const Account&); // new function that takes a const reference

    Record lookup(Account*);
    Record lookup(const Account*); // new function, takes a pointer to const

  • If a parameter has a default argument, all the parameters that follow it must also have default arguments. To override the default for the last parameter, we must also supply arguments for the first several parameters (if exist). So we may design the ordering of parameters so that those least likely to use a default value appear first and those most likely to use a default appear last.

  • Names used as default arguments are resolved in the scope of the function declaration.

  • The inline mechanism is meant to optimize small, straight-line functions that are called frequnetly. The specification is only a request to the compiler. The compiler may choose to ignore this request.

Chapter 7. Classes

  • Member functions access the object on which they were called through an extra, implicit parameter named this. When we call a member function, this is initialized with the address of the object on which the function was invoked. Any direct use of a member of the class is assumed to be an implicit reference through this. (this is a const pointer.)

  • Although this is implicit, it follows the normal initialization rules, which means that (by default) we cannot bind this to a const object. This means that we cannot call an ordinary member function on a const object. A const following the parameter list (i.e., const member function) indicates that this is a pointer to const.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct Sales_data {
    std::string isbn() const { return bookNo; }
    Sales_data& combine(const Sales_data&);
    double avg_price() const;
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
    };

    Sales_data total;
    total.isbn();
    Sales_data::isbn(&total);
    std::string Sales_data::isbn(const Sales_data *const this) { return this->bookNo; }

  • Unlike other member functions, constructors may not be declared as const. When we create a const object of a class type, the object does not assume its "constness" until after the constructor completes the object's initialization.

  • Classes that have members of built-in or compound type usually should rely on the synthesized default constructor only if all such members have in-class initializers. If we define any constructors, the class will not have a default constructor unless we define that constructor ourselves.

  • = default defines the default constructor. We are defining this constructor only because we want to provide other constructors as well as the default constructor. We want this constructor to do exactly the same work as the synthesized version we had been using.

  • We can define a class type using either keyword, struct or class. The only difference between them is the default access level. If we use the struct keyword, the members defined before the first access specifier are public; if we use class, then the members are private.
    As a matter of programming style, when we define a class intending for all of its members to be public, we use struct. If we intend to have private members, then we use class.

  • Friend declarations may appear only inside a class definition. Friends are not members of the class and are not affected by the access control of the section in which they are declared.

  • Unlike ordinary members, members that define types must appear before they are used.

  • It sometimes (but not very often) happens that a class has a data member that we want to be able to modify, even inside a const member function. We indicate such members by including the mutable keyword in their declaration. The basis for this rule is that if a class requires control to initialize an object in one case, then the class is likely to require control in all cases.

  • When we provide an in-class initializer, we must do so following an = sign or inside braces. (See this post.)

  • A class can also make another class its friend or it can declare specific member functions of another (previously defined) class as friends. It is important to understand that friendship is not transitive.

  • A class must declare as a friend each function in a set of overloaded functions that it wishes to make a friend.

  • Class definitions are processed in two phases:
    • First, the member declarations are compiled.
    • Function bodies are compiled only after the entire class has been seen.

    Member function definitions are processed after the compiler processes all of the declarations in the class.

Chapter 8. The IO Library

Chapter 9. Sequential Containers

Chapter 10. Generic Algorithms

Chapter 11. Associative Containers

Chapter 12. Dynamic Memory