mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-02-03 23:40:14 +08:00
Finish translation(mengxinayan)
File Name: 20210204 A guide to understanding Linux software libraries in C.md Translator: mengxinayan
This commit is contained in:
parent
52e815146c
commit
68fd772811
@ -1,507 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (mengxinayan)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (A guide to understanding Linux software libraries in C)
|
||||
[#]: via: (https://opensource.com/article/21/2/linux-software-libraries)
|
||||
[#]: author: (Marty Kalin https://opensource.com/users/mkalindepauledu)
|
||||
|
||||
A guide to understanding Linux software libraries in C
|
||||
======
|
||||
Software libraries are an easy and sensible way to reuse code.
|
||||
![5 pengiuns floating on iceburg][1]
|
||||
|
||||
Software libraries are a longstanding, easy, and sensible way to reuse code. This article explains how to build libraries from scratch and make them available to clients. Although the two sample libraries target Linux, the steps for creating, publishing, and using these libraries apply to other Unix-like systems.
|
||||
|
||||
The sample libraries are written in C, which is well suited to the task. The Linux kernel is written mostly in C with the rest in assembly language. (The same goes for Windows and Linux cousins such as macOS.) The standard system libraries for input/output, networking, string processing, mathematics, security, data encoding, and so on are likewise written mainly in C. To write a library in C is therefore to write in Linux's native language. Moreover, C sets the mark for performance among high-level languages.
|
||||
|
||||
There are also two sample clients (one in C, the other in Python) to access the libraries. It's no surprise that a C client can access a library written in C, but the Python client illustrates that a library written in C can serve clients from other languages.
|
||||
|
||||
### Static versus dynamic libraries
|
||||
|
||||
Linux systems have two types of libraries:
|
||||
|
||||
* A **static library (aka library archive)** is baked into a statically compiled client (e.g., one in C or Rust) during the compilation process' link phase. In effect, each client gets its own copy of the library. A significant downside of a static library comes to the fore if the library needs to be revised (for example, to fix a bug), as each library client must be relinked to the static library. A dynamic library, described next, avoids this shortcoming.
|
||||
* A **dynamic (aka shared) library** is flagged during a statically compiled client program's link phase, but the client program and the library code remain otherwise unconnected until runtime—the library code is not baked into the client. At runtime, the system's dynamic loader connects a shared library with an executing client, regardless of whether the client comes from a statically compiled language, such as C, or a dynamically compiled language, such as Python. As a result, a dynamic library can be updated without inconveniencing clients. Finally, multiple clients can share a single copy of a dynamic library.
|
||||
|
||||
|
||||
|
||||
In general, dynamic libraries are preferred over static ones, although there is a cost in complexity and performance. Here's how each library type is created and published:
|
||||
|
||||
1. The source code for the library is compiled into one or more object modules, which are binary files that can be included in a library and linked to executable clients.
|
||||
2. The object modules are packaged into a single file. For a static library, the standard extension is `.a` for "archive." For a dynamic library, the extension is `.so` for "shared object." The two sample libraries, which have the same functionality, are published as the files `libprimes.a` (static) and `libshprimes.so` (dynamic). The prefix `lib` is used for both types of library.
|
||||
3. The library file is copied to a standard directory so that client programs, without fuss, can access the library. A typical location for the library, whether static or dynamic, is `/usr/lib` or `/usr/local/lib`; other locations are possible.
|
||||
|
||||
|
||||
|
||||
Detailed steps for building and publishing each type of library are coming shortly. First, however, I will introduce the C functions in the two libraries.
|
||||
|
||||
### The sample library functions
|
||||
|
||||
The two sample libraries are built from the same five C functions, four of which are accessible to client programs. The fifth function, which is a utility for one of the other four, shows how C supports hiding information. The source code for each function is short enough that the functions can be housed in a single source file, although multiple source files (e.g., one per each of the four published functions) is an option.
|
||||
|
||||
The library functions are elementary and deal, in various ways, with prime numbers. All of the functions expect unsigned (i.e., non-negative) integer values as arguments:
|
||||
|
||||
* The `is_prime` function tests whether its single argument is a prime.
|
||||
* The `are_coprimes` function checks whether its two arguments have a greatest common divisor (gcd) of 1, which defines co-primes.
|
||||
* The `prime_factors` function lists the prime factors of its argument.
|
||||
* The `goldbach` function expects an even integer value of 4 or more, listing whichever two primes sum to this argument; there may be multiple summing pairs. The function is named after the 18th-century mathematician [Christian Goldbach][2], whose conjecture that every even integer greater than two is the sum of two primes remains one of the oldest unsolved problems in number theory.
|
||||
|
||||
|
||||
|
||||
The utility function `gcd` resides in the deployed library files, but this function is not accessible outside of its containing file; hence, a library client cannot directly invoke the `gcd` function. A closer look at C functions clarifies the point.
|
||||
|
||||
### More on C functions
|
||||
|
||||
Every function in C has a storage class, which determines the function's scope. For functions there are two options:
|
||||
|
||||
* The default storage class for functions is `extern`, which gives a function global scope. A client program can call any `extern` function in the sample libraries. Here's the definition for the function `are_coprimes` with an explicit `extern`: [code] extern unsigned are_coprimes(unsigned n1, unsigned n2) {
|
||||
...
|
||||
}
|
||||
```
|
||||
* The storage class `static` limits a function's scope to the file in which the function is defined. In the sample libraries, the utility function `gcd` is `static`: [code] static unsigned gcd(unsigned n1, unsigned n2) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
Only functions within the `primes.c` file can invoke `gcd`, and only the function `are_coprimes` does so. When the static and dynamic libraries are built and published, other programs can call an `extern` function such as `are_coprimes` but not the `static` function `gcd`. The `static` storage class thus hides the `gcd` function from library clients by limiting the function's scope to the other library functions.
|
||||
|
||||
The functions other than `gcd` in the `primes.c` file need not specify a storage class, which would default to `extern`. However, it is common in libraries to make the `extern` explicit.
|
||||
|
||||
C distinguishes between function definitions and declarations, which is important for libraries. Let's start with definitions. C has named functions only, and every function is defined with:
|
||||
|
||||
* A unique name. No two functions in a program can have the same name.
|
||||
* An argument list, which may be empty. The arguments are typed.
|
||||
* Either a return value type (e.g., `int` for a 32-bit signed integer) or `void` if there is no value returned.
|
||||
* A body enclosed in curly braces. In a contrived example, the body could be empty.
|
||||
|
||||
|
||||
|
||||
Every function in a program must be defined exactly once.
|
||||
|
||||
Here's the full definition for the library function `are_coprimes`:
|
||||
|
||||
|
||||
```
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2) { /* definition */
|
||||
return 1 == gcd(n1, n2); /* greatest common divisor of 1? */
|
||||
}
|
||||
```
|
||||
|
||||
The function returns a boolean value (either 0 for false or 1 for true), depending on whether the two integer arguments have a greatest common divisor of 1. The utility function `gcd` computes the greatest common divisor of integer arguments `n1` and `n2`.
|
||||
|
||||
A function declaration, unlike a definition, does not have a body:
|
||||
|
||||
|
||||
```
|
||||
`extern unsigned are_coprimes(unsigned n1, unsigned n2); /* declaration */`
|
||||
```
|
||||
|
||||
The declaration ends with a semicolon after the argument list; there are no curly braces enclosing a body. A function may be declared multiple times within a program.
|
||||
|
||||
Why are declarations needed at all? In C, a called function must be visible to its caller. There are various ways to provide such visibility, depending on how fussy the compiler is. One sure way is to define the called function above its caller when both reside in the same file:
|
||||
|
||||
|
||||
```
|
||||
void f() {...} /* f is defined before being called */
|
||||
void g() { f(); } /* ok */
|
||||
```
|
||||
|
||||
The definition of function `f` could be moved below the call from function `g` if `f` were declared above the call:
|
||||
|
||||
|
||||
```
|
||||
void f(); /* declaration makes f visible to caller */
|
||||
void g() { f(); } /* ok */
|
||||
void f() {...} /* easier to put this above the call from g */
|
||||
```
|
||||
|
||||
But what if the called function resides in a different file than its caller? How are functions defined in one file made visible in another file, given that each function must be defined exactly once in a program?
|
||||
|
||||
This issue impacts libraries, whether static or dynamic. For example, the functions in the two primes libraries are defined in the source file `primes.c`, binary copies of which are in each library; but these defined functions must be visible to a library client in C, which is a separate program with its own source file(s).
|
||||
|
||||
Providing visibility across files is what function declarations can do. For the "primes" examples, there is a header file named `primes.h` that declares the four functions to be made visible to library clients in C:
|
||||
|
||||
|
||||
```
|
||||
/** header file primes.h: function declarations **/
|
||||
extern unsigned is_prime(unsigned);
|
||||
extern void prime_factors(unsigned);
|
||||
extern unsigned are_coprimes(unsigned, unsigned);
|
||||
extern void goldbach(unsigned);
|
||||
```
|
||||
|
||||
These declarations serve as an interface by specifying the invocation syntax for each function.
|
||||
|
||||
For client convenience, the text file `primes.h` could be stored in a directory on the C compiler's search path. Typical locations are `/usr/include` and `/usr/local/include`. A C client would `#include` this header file near the top of the client's source code. (A header file is thus imported into the "head" of another source file.) C header files also serve as input to utilities (e.g., Rust's `bindgen`) that enable clients in other languages to access a C library.
|
||||
|
||||
In summary, a library function is defined exactly once but declared wherever needed; any library client in C needs the declaration. A header file should contain function declarations—but not function definitions. If a header file did contain definitions, the file might be included multiple times in a C program, thereby breaking the rule that a function must be defined exactly once in a C program.
|
||||
|
||||
### The library source code
|
||||
|
||||
Below is the source code for two libraries. This code, the header file, and the two sample clients are available on my [website][3].
|
||||
|
||||
**The library functions**
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
extern unsigned is_prime(unsigned n) {
|
||||
if (n <= 3) return n > 1; /* 2 and 3 are prime */
|
||||
if (0 == (n % 2) || 0 == (n % 3)) return 0; /* multiples of 2 or 3 aren't */
|
||||
|
||||
/* check that n is not a multiple of other values < n */
|
||||
unsigned i;
|
||||
for (i = 5; (i * i) <= n; i += 6)
|
||||
if (0 == (n % i) || 0 == (n % (i + 2))) return 0; /* not prime */
|
||||
|
||||
return 1; /* a prime other than 2 or 3 */
|
||||
}
|
||||
|
||||
extern void prime_factors(unsigned n) {
|
||||
/* list 2s in n's prime factorization */
|
||||
while (0 == (n % 2)) {
|
||||
[printf][4]("%i ", 2);
|
||||
n /= 2;
|
||||
}
|
||||
|
||||
/* 2s are done, the divisor is now odd */
|
||||
unsigned i;
|
||||
for (i = 3; i <= [sqrt][5](n); i += 2) {
|
||||
while (0 == (n % i)) {
|
||||
[printf][4]("%i ", i);
|
||||
n /= i;
|
||||
}
|
||||
}
|
||||
|
||||
/* one more prime factor? */
|
||||
if (n > 2) [printf][4]("%i", n);
|
||||
}
|
||||
|
||||
/* utility function: greatest common divisor */
|
||||
static unsigned gcd(unsigned n1, unsigned n2) {
|
||||
while (n1 != 0) {
|
||||
unsigned n3 = n1;
|
||||
n1 = n2 % n1;
|
||||
n2 = n3;
|
||||
}
|
||||
return n2;
|
||||
}
|
||||
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2) {
|
||||
return 1 == gcd(n1, n2);
|
||||
}
|
||||
|
||||
extern void goldbach(unsigned n) {
|
||||
/* input errors */
|
||||
if ((n <= 2) || ((n & 0x01) > 0)) {
|
||||
[printf][4]("Number must be > 2 and even: %i is not.\n", n);
|
||||
return;
|
||||
}
|
||||
|
||||
/* two simple cases: 4 and 6 */
|
||||
if ((4 == n) || (6 == n)) {
|
||||
[printf][4]("%i = %i + %i\n", n, n / 2, n / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* for n >= 8: multiple possibilities for many */
|
||||
unsigned i;
|
||||
for (i = 3; i < (n / 2); i++) {
|
||||
if (is_prime(i) && is_prime(n - i)) {
|
||||
[printf][4]("%i = %i + %i\n", n, i, n - i);
|
||||
/* if one pair is enough, replace this with break */
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These functions serve as grist for the library mill. The two libraries derive from exactly the same source code, and the header file `primes.h` is the C interface for both libraries.
|
||||
|
||||
### Building the libraries
|
||||
|
||||
The steps for building and publishing static and dynamic libraries differ in a few details. Only three steps are required for the static library and just two more for the dynamic library. The additional steps in building the dynamic library reflect the added flexibility of the dynamic approach. Let's start with the static library.
|
||||
|
||||
The library source file `primes.c` is compiled into an object module. Here's the command, with the percent sign as the system prompt (double sharp signs introduce my comments):
|
||||
|
||||
|
||||
```
|
||||
`% gcc -c primes.c ## step 1 static`
|
||||
```
|
||||
|
||||
This produces the binary file `primes.o`, the object module. The flag `-c` means compile only.
|
||||
|
||||
The next step is to archive the object module(s) by using the Linux `ar` utility:
|
||||
|
||||
|
||||
```
|
||||
`% ar -cvq libprimes.a primes.o ## step 2 static`
|
||||
```
|
||||
|
||||
The three flags `-cvq` are short for "create," "verbose," and "quick append" (in case new files must be added to an archive). Recall that the prefix `lib` is standard, but the library name is arbitrary. Of course, the file name for a library must be unique to avoid conflicts.
|
||||
|
||||
The archive is ready to be published:
|
||||
|
||||
|
||||
```
|
||||
`% sudo cp libprimes.a /usr/local/lib ## step 3 static`
|
||||
```
|
||||
|
||||
The static library is now accessible to clients, examples of which are forthcoming. (The `sudo` is included to ensure the correct access rights for copying a file into `/usr/local/lib`.)
|
||||
|
||||
The dynamic library also requires one or more object modules for packaging:
|
||||
|
||||
|
||||
```
|
||||
`% gcc primes.c -c -fpic ## step 1 dynamic`
|
||||
```
|
||||
|
||||
The added flag `-fpic` directs the compiler to generate position-independent code, which is a binary module that need not be loaded into a fixed memory location. Such flexibility is critical in a system of multiple dynamic libraries. The resulting object module is slightly larger than the one generated for the static library.
|
||||
|
||||
Here's the command to create the single library file from the object module(s):
|
||||
|
||||
|
||||
```
|
||||
`% gcc -shared -Wl,-soname,libshprimes.so -o libshprimes.so.1 primes.o ## step 2 dynamic`
|
||||
```
|
||||
|
||||
The flag `-shared` indicates that the library is shared (dynamic) rather than static. The `-Wl` flag introduces a list of compiler options, the first of which sets the dynamic library's `soname`, which is required. The `soname` first specifies the library's logical name (`libshprimes.so`) and then, following the `-o` flag, the library's physical file name (`libshprimes.so.1`). The goal to is keep the logical name constant while allowing the physical file name to change with new versions. In this example, the 1 at the end of the physical file name `libshprimes.so.1` represents the first version of the library. The logical and physical file names could be the same, but best practice is to have separate names. A client accesses the library through its logical name (in this case, `libshprimes.so`), as I will clarify shortly.
|
||||
|
||||
The next step is to make the shared library easily accessible to clients by copying it to the appropriate directory; for example, `/usr/local/lib again:`
|
||||
|
||||
|
||||
```
|
||||
`% sudo cp libshprimes.so.1 /usr/local/lib ## step 3 dynamic`
|
||||
```
|
||||
|
||||
A symbolic link is now set up between the shared library's logical name (`libshprimes.so`) and its full physical file name (`/usr/local/lib/libshprimes.so.1`). It's easiest to give the command with `/usr/local/lib` as the working directory:
|
||||
|
||||
|
||||
```
|
||||
`% sudo ln --symbolic libshprimes.so.1 libshprimes.so ## step 4 dynamic`
|
||||
```
|
||||
|
||||
The logical name `libshprimes.so` should not change, but the target of the symbolic link (`libshrimes.so.1`) can be updated as needed for new library implementations that fix bugs, boost performance, and so on.
|
||||
|
||||
The final step (a precautionary one) is to invoke the `ldconfig` utility, which configures the system's dynamic loader. This configuration ensures that the loader will find the newly published library:
|
||||
|
||||
|
||||
```
|
||||
`% sudo ldconfig ## step 5 dynamic`
|
||||
```
|
||||
|
||||
The dynamic library is now ready for clients, including the two sample ones that follow.
|
||||
|
||||
### A C library client
|
||||
|
||||
The sample C client is the program tester, whose source code begins with two `#include` directives:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h> /* standard input/output functions */
|
||||
#include <primes.h> /* my library functions */
|
||||
```
|
||||
|
||||
The angle brackets around the file names indicate that these header files are to be found on the compiler's search path (in the case of `primes.h`, the directory `/usr/local/include`). Without this `#include`, the compiler would complain about missing declarations for functions such as `is_prime` and `prime_factors`, which are published in both libraries. By the way, the source code for the tester program need not change at all to test each of the two libraries.
|
||||
|
||||
By contrast, the source file for the library (`primes.c`) opens with these `#include` directives:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
```
|
||||
|
||||
The header file `math.h` is required because the library function `prime_factors` calls the mathematics function `sqrt` in the standard library `libm.so`.
|
||||
|
||||
For reference, here is the source code for the tester program:
|
||||
|
||||
**The tester program**
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <primes.h>
|
||||
|
||||
int main() {
|
||||
/* is_prime */
|
||||
[printf][4]("\nis_prime\n");
|
||||
unsigned i, count = 0, n = 1000;
|
||||
for (i = 1; i <= n; i++) {
|
||||
if (is_prime(i)) {
|
||||
count++;
|
||||
if (1 == (i % 100)) [printf][4]("Sample prime ending in 1: %i\n", i);
|
||||
}
|
||||
}
|
||||
[printf][4]("%i primes in range of 1 to a thousand.\n", count);
|
||||
|
||||
/* prime_factors */
|
||||
[printf][4]("\nprime_factors\n");
|
||||
[printf][4]("prime factors of 12: ");
|
||||
prime_factors(12);
|
||||
[printf][4]("\n");
|
||||
|
||||
[printf][4]("prime factors of 13: ");
|
||||
prime_factors(13);
|
||||
[printf][4]("\n");
|
||||
|
||||
[printf][4]("prime factors of 876,512,779: ");
|
||||
prime_factors(876512779);
|
||||
[printf][4]("\n");
|
||||
|
||||
/* are_coprimes */
|
||||
[printf][4]("\nare_coprime\n");
|
||||
[printf][4]("Are %i and %i coprime? %s\n",
|
||||
21, 22, are_coprimes(21, 22) ? "yes" : "no");
|
||||
[printf][4]("Are %i and %i coprime? %s\n",
|
||||
21, 24, are_coprimes(21, 24) ? "yes" : "no");
|
||||
|
||||
/* goldbach */
|
||||
[printf][4]("\ngoldbach\n");
|
||||
goldbach(11); /* error */
|
||||
goldbach(4); /* small one */
|
||||
goldbach(6); /* another */
|
||||
for (i = 100; i <= 150; i += 2) goldbach(i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In compiling `tester.c` into an executable, the tricky part is the order of the link flags. Recall that the two sample libraries begin with the prefix `lib`, and each has the usual extension: `.a` for the static library `libprimes.a` and `.so` for the dynamic library `libshprimes.so`. In a links specification, the prefix `lib` and the extension fall away. A link flag begins with `-l` (lowercase L), and a compilation command may contain many link flags. Here is the full compilation command for the tester program, using the dynamic library as the example:
|
||||
|
||||
|
||||
```
|
||||
`% gcc -o tester tester.c -lshprimes -lm`
|
||||
```
|
||||
|
||||
The first link flag identifies the library `libshprimes.so` and the second link flag identifies the standard mathematics library `libm.so`.
|
||||
|
||||
The linker is lazy, which means that the order of the link flags matters. For example, reversing the order of the link specifications generates a compile-time error:
|
||||
|
||||
|
||||
```
|
||||
`% gcc -o tester tester.c -lm -lshprimes ## danger!`
|
||||
```
|
||||
|
||||
The flag that links to `libm.so` comes first, but no function from this library is invoked explicitly in the tester program; hence, the linker does not link to the `math.so` library. The call to the `sqrt` library function occurs only in the `prime_factors` function that is now contained in the `libshprimes.so` library. The resulting error in compiling the tester program is:
|
||||
|
||||
|
||||
```
|
||||
`primes.c: undefined reference to 'sqrt'`
|
||||
```
|
||||
|
||||
Accordingly, the order of the link flags should notify the linker that the `sqrt` function is needed:
|
||||
|
||||
|
||||
```
|
||||
`% gcc -o tester tester.c -lshprimes -lm ## -lshprimes 1st`
|
||||
```
|
||||
|
||||
The linker picks up the call to the library function `sqrt` in the `libshprimes.so` library and, therefore, does the appropriate link to the mathematics library `libm.so`. There is a more complicated option for linking that supports either link-flag order; in this case, however, the easy way is to arrange the link flags appropriately.
|
||||
|
||||
Here is some output from a run of the tester client:
|
||||
|
||||
|
||||
```
|
||||
is_prime
|
||||
Sample prime ending in 1: 101
|
||||
Sample prime ending in 1: 401
|
||||
...
|
||||
168 primes in range of 1 to a thousand.
|
||||
|
||||
prime_factors
|
||||
prime factors of 12: 2 2 3
|
||||
prime factors of 13: 13
|
||||
prime factors of 876,512,779: 211 4154089
|
||||
|
||||
are_coprime
|
||||
Are 21 and 22 coprime? yes
|
||||
Are 21 and 24 coprime? no
|
||||
|
||||
goldbach
|
||||
Number must be > 2 and even: 11 is not.
|
||||
4 = 2 + 2
|
||||
6 = 3 + 3
|
||||
...
|
||||
32 = 3 + 29
|
||||
32 = 13 + 19
|
||||
...
|
||||
100 = 3 + 97
|
||||
100 = 11 + 89
|
||||
...
|
||||
```
|
||||
|
||||
For the `goldbach` function, even a relatively small even value (e.g., 18) may have multiple pairs of primes that sum to it (in this case, 5+13 and 7+11). Such multiple prime pairs are among the factors that complicate an attempted proof of Goldbach's conjecture.
|
||||
|
||||
### Wrapping up with a Python client
|
||||
|
||||
Python, unlike C, is not a statically compiled language, which means that the sample Python client must access the dynamic rather than the static version of the primes library. To do so, Python has various modules (standard and third-party) that support a foreign function interface (FFI), which allows a program written in one language to invoke functions written in another. Python `ctypes` is a standard and relatively simple FFI that enables Python code to call C functions.
|
||||
|
||||
Any FFI has challenges because the interfacing languages are unlikely to have exactly the same data types. For example, the primes library uses the C type `unsigned int`, which Python does not have; the `ctypes` FFI maps a C `unsigned int` to a Python `int`. Of the four `extern` C functions published in the primes library, two behave better in Python with explicit `ctypes` configuration.
|
||||
|
||||
The C functions `prime_factors` and `goldbach` have `void` instead of a return type, but `ctypes` by default replaces the C `void` with the Python `int`. When called from Python code, the two C functions then return a random (hence, meaningless) integer value from the stack. However, `ctypes` can be configured to have the functions return `None` (Python's null type) instead. Here's the configuration for the `prime_factors` function:
|
||||
|
||||
|
||||
```
|
||||
`primes.prime_factors.restype = None`
|
||||
```
|
||||
|
||||
A similar statement handles the `goldbach` function.
|
||||
|
||||
The interactive session below (in Python 3) shows that the interface between a Python client and the primes library is straightforward:
|
||||
|
||||
|
||||
```
|
||||
>>> from ctypes import cdll
|
||||
|
||||
>>> primes = cdll.LoadLibrary("libshprimes.so") ## logical name
|
||||
|
||||
>>> primes.is_prime(13)
|
||||
1
|
||||
>>> primes.is_prime(12)
|
||||
0
|
||||
|
||||
>>> primes.are_coprimes(8, 24)
|
||||
0
|
||||
>>> primes.are_coprimes(8, 25)
|
||||
1
|
||||
|
||||
>>> primes.prime_factors.restype = None
|
||||
>>> primes.goldbach.restype = None
|
||||
|
||||
>>> primes.prime_factors(72)
|
||||
2 2 2 3 3
|
||||
|
||||
>>> primes.goldbach(32)
|
||||
32 = 3 + 29
|
||||
32 = 13 + 19
|
||||
```
|
||||
|
||||
The functions in the primes library use only a simple data type, `unsigned int`. If this C library used complicated types such as structures, and if pointers to structures were passed to and returned from library functions, then an FFI more powerful than `ctypes` might be better for a smooth interface between Python and C. Nonetheless, the `ctypes` example shows that a Python client can use a library written in C. Indeed, the popular NumPy library for scientific computing is written in C and then exposed in a high-level Python API.
|
||||
|
||||
The simple primes library and the advanced NumPy library underscore that C remains the lingua franca among programming languages. Almost every language can talk to C—and, through C, to any other language that talks to C. Python talks easily to C and, as another example, Java may do the same when [Project Panama][6] becomes an alternative to Java Native Interface (JNI).
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/linux-software-libraries
|
||||
|
||||
作者:[Marty Kalin][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[萌新阿岩](https://github.com/mengxinayan)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/mkalindepauledu
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/rh_003499_01_linux31x_cc.png?itok=Pvim4U-B (5 pengiuns floating on iceburg)
|
||||
[2]: https://en.wikipedia.org/wiki/Christian_Goldbach
|
||||
[3]: https://condor.depaul.edu/mkalin
|
||||
[4]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
|
||||
[5]: http://www.opengroup.org/onlinepubs/009695399/functions/sqrt.html
|
||||
[6]: https://openjdk.java.net/projects/panama
|
@ -0,0 +1,483 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (mengxinayan)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (A guide to understanding Linux software libraries in C)
|
||||
[#]: via: (https://opensource.com/article/21/2/linux-software-libraries)
|
||||
[#]: author: (Marty Kalin https://opensource.com/users/mkalindepauledu)
|
||||
|
||||
理解 C 语言中的 Linux 软件库指南
|
||||
======
|
||||
软件库是一种简单而又明智的方式来复用代码。
|
||||
|
||||
![5 pengiuns floating on iceburg][1]
|
||||
|
||||
软件库是一种是长期支持的、简单的和明智的方式来复用代码。这篇文章解释了如何从头开始构建库并使得其可用。尽管两个示例库都以 Linux 为例,但创建、发布和使用这些库的步骤也可以应用于类似 Unix 系统。
|
||||
|
||||
示例库使用 C 语言编写,非常适合该任务。Linux 内核大部分由 C 语言和少量汇编语言编写(Windows 和 Linux 的表弟如 macOS 也是如此)。用于输入/输出、网络、字符串处理、数学、安全、数据编码和其他标准系统库等主要由 C 语言编写。所以使用 C 语言编写库就是使用 Linux 的原生语言来编写。除此之外,C 被认为高性能在所有高级语言中。
|
||||
|
||||
有两个示例(一个使用 C,另一个使用 Python)来访问库。毫无疑问可以使用 C 语言程序来访问 C 语言编写的库,但是 Python 程序示例说明了一个由 C 语言编写库可以服务于其他编程语言。
|
||||
|
||||
### 静态库和动态库对比
|
||||
|
||||
Linux 系统存在两种类型库:
|
||||
|
||||
* **静态库(也被称为归档库)**:在编译过程中的链接阶段,静态库会被编译进程序(例如C或Rust)中。每个程序都有属于自己的一份库的拷贝。静态库有一个显而易见的缺点——当程序需要进行一定改动时(例如修复一个bug),静态库必须重新链接一次。接下来要介绍的动态库避免了这一缺点。
|
||||
* **动态库(也被称为共享库)**:动态库首先会在程序编译中的链接阶段被标记,但是应用程序和库代码直到运行时才会进行连接,且库代码不会进入到程序中。系统动态加载器将会把一个共享库和正在运行的程序进行连接,无论该程序是由静态编译如 C 编写,还是有动态解释语言如 Python 编写。因此,动态库不需要麻烦程序便可以进行更新。最后,多个程序可以共享同一个动态库。
|
||||
|
||||
通常来说,动态库优于静态库,尽管其复杂性较高和性能较低。下面是两种类型的库如何创建和发布:
|
||||
|
||||
1. 库的源代码会被编译进一个或多个目标模块,目标模块可以被包含在库中并且链接到程序的二进制文件中。
|
||||
2. 目标模块将会被打包成一个文件。对于静态库,标准的文件拓展名是 `.a` 意为”归档“;对于动态库,标准的文件拓展名是 `.so` 意为”共享目标“。对于相同功能的两个示例库,分别发布为 `libprimes.a` (静态库)和 `libshprimes.so` (动态库)。两种库的文件名都使用前缀 `lib` 进行标识。
|
||||
3. 在标准目录下拷贝一份库文件,使得程序可以轻松地访问到库。无论是静态库还是动态库,一个经典的位置是 `/usr/lib` 或者 `/usr/local/lib`,当让其他地址也是可以的。
|
||||
|
||||
构建和发布每种库地具体步骤会在下面详细介绍。首先我将介绍两种库里涉及到的 C 函数。
|
||||
|
||||
### 示例库函数
|
||||
|
||||
两个示例库都是从五个相同的 C 函数构建而成的,其中四个函数可供客户程序使用。第五函数为其他四个函数的实用功能,它显示了 C 语言怎么支持隐藏信息。每个函数的源代码都足够得短,可以将其存在在单个源文件中,或者多个源文件中(每个文件存储一个函数)
|
||||
|
||||
库函数是基本的处理函数,用多种方式处理指数。所有的函数接收非负整数值作为参数:
|
||||
|
||||
- `is_prime` 函数测试其单个参数是否为质数。
|
||||
- `are_coprimes` 函数检查了其两个参数的最大公约数(gcd)是否为1,即是否为互质数。
|
||||
- `prime_factors`:函数列出其参数的质因数。
|
||||
- `glodbach`:函数接收一个大于等于 4 的偶数,列出其可以分解为两个质数的和。它也许存在多个符合条件的数对。该函数是以 18 世纪数学家 [克里斯蒂安·哥德巴赫][2] 命名的,他的猜想是任意一个大于 2 的偶数可以分解为两个质数之和,这依旧是数论里最古老未被解决的问题。
|
||||
|
||||
工具函数 `gcd` 留在已部署的库文件中,但是没有包含这个函数的文件无法访问此函数。因此,一个使用库的客户程序无法调用 `gcd` 函数。仔细观察 C 函数可以明白这一点。
|
||||
|
||||
### 更多关于 C 函数的内容
|
||||
|
||||
每个在 C 语言中的函数都有一个存储类,它决定了函数的范围。对于函数,有两种选择。
|
||||
|
||||
- 函数默认的存储类是`extern`,它给了函数一个全局域。一个客户端程序可以调用任意 `extern` 修饰的函数在示例库中。下面是一个带有显示 `extern` 声明的 `are_coprimes` 函数定义:
|
||||
|
||||
Every function in C has a storage class, which determines the function's scope. For functions there are two options:
|
||||
|
||||
```c
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- 存储类 `static` 将一个函数的的范围限制到函数被定义的文件中。在示例库中,工具函数 `gcd` 是静态的(`static`):
|
||||
|
||||
```c
|
||||
static unsigned gcd(unsigned n1, unsigned n2) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
只有在 `primes.c` 文件中的函数可以调用 `gcd`,而只有 `are_coprimes` 函数会调用它。当静态库和动态库被构建和发布后,其他的程序可以调用外部的(`extern`)函数像 `are_coprimes` ,但是不可以调用静态(`static`)函数像`gcd`。静态(`static`)存储类通过限制函数范围到其他库函数内,进而实现了对库的用户程序隐藏 `gcd` 函数。
|
||||
|
||||
在 `primes.c` 文件中除了 `gcd` 函数外,其他函数并没有指明存储类,默认将会设置为 外部的(`extern`)。然而,在库中显示注明 `extern` 是更加常见的。
|
||||
|
||||
C 语言通过函数定义和声明来分别函数,这对库来说是重要的。接下来让我们开始了解定义。C语言仅允许命名函数不允许匿名函数,并且每个函数需要定义以下内容:
|
||||
|
||||
- 一个唯一的名字。一个程序的不允许存在两个同名的函数。
|
||||
- 一个可以为空的参数列表。参数需要指明类型。
|
||||
- 一个返回值类型(例如:int代表32位有符号整数),当没有返回值时设置为空类型(void)
|
||||
- 用一对花括号包围起来的函数主体部分。在一个人为的示例中,函数主体部分可以为空。
|
||||
|
||||
程序中的每个函数必须要被定义一次。
|
||||
|
||||
下面是库函数 `are_coprimes` 的完整定义:
|
||||
|
||||
```c
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2) { /* 定义 */
|
||||
return 1 == gcd(n1, n2); /* 最大公约数是否为1? */
|
||||
}
|
||||
```
|
||||
|
||||
函数返回一个布尔值(0代表假或者1代表真),取决于两个整数参数值的最大公约数是否为1。工具函数 `gcd` 计算两个整数参数 `n1` 和 `n2` 的最大公约数。
|
||||
|
||||
一个函数声明不同于定义,其不需要主体部分:
|
||||
|
||||
```c
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2); /* 声明 */
|
||||
```
|
||||
|
||||
声明在参数列表后用一个分号代表结束,它没有被花括号包围起来的主体部分。一个程序中的一个函数可以被多次声明。
|
||||
|
||||
为什么需要声明?在 C 语言中,一个被调用的函数必须对其调用者可见。有多种方式可以提供这样的可见性,具体依赖于编译器如何实现。一个必然可行的方式就是当它们二者位于同一个文件中时,将被调用的函数定义在在它的调用者之前。
|
||||
|
||||
```c
|
||||
void f() {...} /* f 定义在其被调用前 */
|
||||
void g() { f(); } /* ok */
|
||||
```
|
||||
|
||||
当函数 `f` 被在调用前声明,此时函数 `f` 的定义可以移动到函数 `g` 的下方。
|
||||
|
||||
```c
|
||||
void f(); /* 声明使得函数 f 对调用者可见 */
|
||||
void g() { f(); } /* ok */
|
||||
void f() {...} /* 相较于前一种方式,此方式显得更简洁 */
|
||||
```
|
||||
|
||||
但是当如果一个被调用的函数和调用它的函数不在同一个文件中时呢?因为前文提到一个函数在一个程序中需要被定义一次,那么如何使得让一个文件中被定义的函数在另一个文件中可见?
|
||||
|
||||
这个问题会影响库,无论是静态库还是动态库。例如在两个质数库中函数被定义在源文件 `primes.c` 中,每个库中都有该函数的二进制拷贝,但是这些定义的函数必须要对使用库的 C 程序可见,该 C 程序有其自身的源文件。
|
||||
|
||||
函数声明可以帮助提供跨文件的可见性。对于上述的“质数”例子,它有一个名为 `primes.h` 的头文件,其声明了四个函数使得它们对使用库的 C 程序可见。
|
||||
|
||||
```c
|
||||
/** 头文件 primes.h:函数声明 **/
|
||||
extern unsigned is_prime(unsigned);
|
||||
extern void prime_factors(unsigned);
|
||||
extern unsigned are_coprimes(unsigned, unsigned);
|
||||
extern void goldbach(unsigned);
|
||||
```
|
||||
|
||||
这些声明通过为每个函数指定其调用语法来作为接口。
|
||||
|
||||
为了客户程序的便利性,头文件 `primes.h` 应该存储在 C 编译器查找路径下的目录中。典型的位置有 `/usr/include` 和 `/usr/local/include`。一个 C 语言客户程序应使用 `#include` 包含这个头文件,并尽可能将这条语句其程序源代码的首部(一个头文件将会被导入另一个源文件的“头”部)。C 语言头文件可以被导入其他语言如Rust语言中的 `bindgen`,以使得用户可以使用其他语言来访问 C 语言库。
|
||||
|
||||
总之,一个库函数只可以被定义一次,但可以在任何需要它的地方进行声明,任一使用C语言库的程序都需要事先声明。一个头文件可以包含函数声明,但不能包含函数定义。如果一个头文件包含了函数定义,那么文件可能会被包含多次在一个 C 语言程序中。
|
||||
|
||||
### 库的源代码
|
||||
|
||||
下面是两个库的源代码。这部分代码、头文件、以及两个示例客户程序都可以在 [我的网页][3] 上找到。
|
||||
|
||||
**库函数**
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
extern unsigned is_prime(unsigned n) {
|
||||
if (n <= 3) return n > 1; /* 2和3是质数 */
|
||||
if (0 == (n % 2) || 0 == (n % 3)) return 0; /* 2和3的倍数不会是质数 */
|
||||
|
||||
/* check that n is not a multiple of other values < n */
|
||||
unsigned i;
|
||||
for (i = 5; (i * i) <= n; i += 6)
|
||||
if (0 == (n % i) || 0 == (n % (i + 2))) return 0; /* 不是质数 */
|
||||
|
||||
return 1; /* 一个不是2和3的质数 */
|
||||
}
|
||||
|
||||
extern void prime_factors(unsigned n) {
|
||||
/* 在数字 n 的质因数分解中列出所有 2 */
|
||||
while (0 == (n % 2)) {
|
||||
printf("%i ", 2);
|
||||
n /= 2;
|
||||
}
|
||||
|
||||
/* 数字 2 已经处理完成,下面处理奇数 */
|
||||
unsigned i;
|
||||
for (i = 3; i <= sqrt(n); i += 2) {
|
||||
while (0 == (n % i)) {
|
||||
printf("%i ", i);
|
||||
n /= i;
|
||||
}
|
||||
}
|
||||
|
||||
/* 还有其他质因数?*/
|
||||
if (n > 2) printf("%i", n);
|
||||
}
|
||||
|
||||
/* 工具函数:计算最大公约数 */
|
||||
static unsigned gcd(unsigned n1, unsigned n2) {
|
||||
while (n1 != 0) {
|
||||
unsigned n3 = n1;
|
||||
n1 = n2 % n1;
|
||||
n2 = n3;
|
||||
}
|
||||
return n2;
|
||||
}
|
||||
|
||||
extern unsigned are_coprimes(unsigned n1, unsigned n2) {
|
||||
return 1 == gcd(n1, n2);
|
||||
}
|
||||
|
||||
extern void goldbach(unsigned n) {
|
||||
/* 输入错误 */
|
||||
if ((n <= 2) || ((n & 0x01) > 0)) {
|
||||
printf("Number must be > 2 and even: %i is not.\n", n);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 两个简单的例子:4和6 */
|
||||
if ((4 == n) || (6 == n)) {
|
||||
printf("%i = %i + %i\n", n, n / 2, n / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 当n>8时,存在多种可能性 */
|
||||
unsigned i;
|
||||
for (i = 3; i < (n / 2); i++) {
|
||||
if (is_prime(i) && is_prime(n - i)) {
|
||||
printf("%i = %i + %i\n", n, i, n - i);
|
||||
/* 如果只需要一对,那么用 break 语句替换这句 */
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这些函数可以被库利用。两个库可以从相同的源代码中获得,同时头文件 `primes.h` 是两个库的 C 语言接口。
|
||||
|
||||
### 构件库
|
||||
|
||||
静态库和动态库在构建和发布的步骤上有一些细节的不同。静态库需要三个步骤而动态库需要增加两个步骤即一共五个步骤。额外的步骤表明了动态库的动态方法具有更多的灵活性。让我们先从静态库开始。
|
||||
|
||||
库源文件 `primes.c` 被编译进一个目标模块。下面是命令,百分号 % 代表系统提示符,两个井字符 # 是我的注释。
|
||||
|
||||
```shell
|
||||
% gcc -c primes.c ## 步骤1(静态)
|
||||
```
|
||||
|
||||
这一步生成目标对象是二进制文件 `primes.o`。`-c` 标志意味着只编译。
|
||||
|
||||
下一步是使用 Linux 的 `ar` 命令将目标对象归档。
|
||||
|
||||
```shell
|
||||
% ar -cvq libprimes.a primes.o ## 步骤2(静态)
|
||||
```
|
||||
|
||||
`-cvq` 三个标识分别是“创建”、“详细的”、“快速添加(以防新文件没有添加到归档中)”。回忆一下,前文提到过前缀 `lib` 是必须的,而库名确是任意的。当然库的文件名必须是唯一的以避免冲突。
|
||||
|
||||
归档已经准备好要被发布:
|
||||
|
||||
```shell
|
||||
% sudo cp libprimes.a /usr/local/lib ## 步骤3(静态)
|
||||
```
|
||||
|
||||
现在静态库对接下来的客户示例程序是可见的。(包含 `sudo` 可以确保有访问权限将文件拷贝进 `/usr/local/lib` 目录中)
|
||||
|
||||
动态库还需要一个或多个对象模块进行打包:
|
||||
|
||||
```shell
|
||||
% gcc primes.c -c -fpic ## 步骤1(动态)
|
||||
```
|
||||
|
||||
增加的选项 `-fpic` 指示编译器生成与位置无关的代码,这意味着不需要将该二进制模块加载到一个固定的内存位置。在一个拥有多个动态库的系统中这种灵活性是至关重要的。生成的对象模块会略大于静态库生成的对象模块。
|
||||
|
||||
下面是从对象模块创建单个库文件的命令:
|
||||
|
||||
```shell
|
||||
% gcc -shared -Wl,-soname,libshprimes.so -o libshprimes.so.1 primes.o ## 步骤2(动态)
|
||||
```
|
||||
|
||||
选项 `-shared` 标明了库是一个共享的(动态的)而不是静态的。`-Wl` 选项引入了一系列编译器选项,第一个便是设置动态库的 `-soname`,这是必须设置的。`soname` 首先指定了库的逻辑名字(`libshprimes.so`),接下来的 `-o` 选项指明了库的物理文件名字(`libshprimes.so.1`)。这样做的目的是为了保持逻辑名不变的同时允许物理名随着新版本而发生变化。在本例中,在物理文件名 `libshprimes.so.1` 中最后的 1 代表是第一个库的版本。尽管逻辑文件名和物理文件名可以是相同的,但是最佳做法是将它们命名为不同的名字。一个客户程序将会通过逻辑名(本例中为`libshprimes.so`)来访问库,稍后我会进一步解释。
|
||||
|
||||
接下来的一步是通过拷贝共享库到合适的目录下使得客户程序容易访问;例如,`/usr/local/lib` 目录下:
|
||||
|
||||
```shell
|
||||
% sudo cp libshprimes.so.1 /usr/local/lib ## 步骤3(动态)
|
||||
```
|
||||
|
||||
现在在共享库的逻辑名(`libshprimes.so`)和它的物理文件名(`/usr/local/lib/libshprimes.so.1`)之间设置一个符号链接。最简单的方式是将 `/usr/local/lib` 作为工作目录,在该目录下输入命令:
|
||||
|
||||
```shell
|
||||
% sudo ln --symbolic libshprimes.so.1 libshprimes.so ## 步骤4(动态)
|
||||
```
|
||||
|
||||
逻辑文件名 `libshprimes.so` 不应该改变,但是符号链接的目标(`libshrimes.so.1`)可以根据需要进行更新,新的库实现可以是修复了bug,提高性能等。
|
||||
|
||||
最后一步(一个预防措施)是调用 `ldconfig` 工具来配置系统的动态加载器。这个配置保证了加载器能够找到新发布的库。
|
||||
|
||||
```shell
|
||||
% sudo ldconfig ## 步骤5(动态)
|
||||
```
|
||||
|
||||
到现在,动态库已为客户准备就绪了,包括接下来的两个示例库。
|
||||
|
||||
### 一个使用库的 C 程序
|
||||
|
||||
示例 C 程序是一个测试程序,它的源代码以两条 `#include` 指令开始:
|
||||
|
||||
```c
|
||||
#include <stdio.h> /* 标准输入/输出函数 */
|
||||
#include <primes.h> /* 我的库函数 */
|
||||
```
|
||||
|
||||
文件名两边的尖括号标识可以在编译器的搜索路径中找到这些头文件(对于 primes.h 文件在 `/usr/local/inlcude` 目录下)。如果不包含 `#include`,编译器会抱怨缺少函数的声明,例如`is_prime` 和 `prime_factors` 函数,它们都在两个库中发布。顺便提一句,测试程序的源代码不需要更改即可测试两个库中的每一个库。
|
||||
|
||||
相比之下,库的源文件(`primes.c`)使用 `#include` 指令打开以下头文件:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
```
|
||||
|
||||
`math.h` 头文件是必须的,因为库函数 `prime_factors` 会调用数学函数 `sqrt`,其在标准库 `libm.so` 中。
|
||||
|
||||
作为参考,这是测试库程序的源代码:
|
||||
|
||||
**测试程序**
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <primes.h>
|
||||
|
||||
int main() {
|
||||
/* is_prime */
|
||||
printf("\nis_prime\n");
|
||||
unsigned i, count = 0, n = 1000;
|
||||
for (i = 1; i <= n; i++) {
|
||||
if (is_prime(i)) {
|
||||
count++;
|
||||
if (1 == (i % 100)) printf("Sample prime ending in 1: %i\n", i);
|
||||
}
|
||||
}
|
||||
printf("%i primes in range of 1 to a thousand.\n", count);
|
||||
|
||||
/* prime_factors */
|
||||
printf("\nprime_factors\n");
|
||||
printf("prime factors of 12: ");
|
||||
prime_factors(12);
|
||||
printf("\n");
|
||||
|
||||
printf("prime factors of 13: ");
|
||||
prime_factors(13);
|
||||
printf("\n");
|
||||
|
||||
printf("prime factors of 876,512,779: ");
|
||||
prime_factors(876512779);
|
||||
printf("\n");
|
||||
|
||||
/* are_coprimes */
|
||||
printf("\nare_coprime\n");
|
||||
printf("Are %i and %i coprime? %s\n",
|
||||
21, 22, are_coprimes(21, 22) ? "yes" : "no");
|
||||
printf("Are %i and %i coprime? %s\n",
|
||||
21, 24, are_coprimes(21, 24) ? "yes" : "no");
|
||||
|
||||
/* goldbach */
|
||||
printf("\ngoldbach\n");
|
||||
goldbach(11); /* error */
|
||||
goldbach(4); /* small one */
|
||||
goldbach(6); /* another */
|
||||
for (i = 100; i <= 150; i += 2) goldbach(i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
在编译 `tester.c` 文件到可执行时,难处理的部分时链接选项的顺序。回想前文中提到两个示例库都是用 `lib` 作为前缀开始,并且每一个都有一个普通的拓展后缀:`.a` 代表静态库 `libprimes.a`,`.so` 代表动态库 `libshprimes.so`。一个具体的链接例子中,前缀 `lib` 和拓展名被忽略了。一个链接标志用 `-l` (小写L)开始,并且一条编译命令可能包含多个链接标志。下面是一个完整的编译测试程序的编译指令,使用动态库作为示例:
|
||||
|
||||
```shell
|
||||
% gcc -o tester tester.c -lshprimes -lm
|
||||
```
|
||||
|
||||
第一个链接标志指定了库 `libshprimes.so`,第二个链接标志指定了标准数学库 `libm.so`。
|
||||
|
||||
链接器是懒惰的,这意味着链接标志的顺序是需要考虑的。例如,调整上述实例中的链接顺序将会产生一个编译时错误:
|
||||
|
||||
```shell
|
||||
% gcc -o tester tester.c -lm -lshprimes ## 危险!
|
||||
```
|
||||
|
||||
链接 `libm.so` 库的标志先出现,但是这个库中没有函数被测试程序显式调用;因此,链接器不会链接到 `math.so` 库。调用 `sqrt` 库函数仅发生在 `libshprimes.so` 库中包含的 `prime_factors` 函数。编译测试程序返回的错误是:
|
||||
|
||||
```
|
||||
primes.c: undefined reference to 'sqrt'
|
||||
```
|
||||
|
||||
因此,链接标志的顺序应该是通知链接器需要 `sqrt` 函数:
|
||||
|
||||
```shell
|
||||
% gcc -o tester tester.c -lshprimes -lm ## 首先链接 -lshprimes
|
||||
```
|
||||
|
||||
链接器在 `libshprimes.so` 库中发现了对库函数 `sqrt` 的调用,所以接下来做了合适的链接到数学库 `libm.so`。链接还有一个更复杂的选项,它支持链接的标志顺序。在本例中,然而简单的方式就是恰当地排列链接标志。
|
||||
|
||||
下面是运行测试程序的部分输出结果:
|
||||
|
||||
```
|
||||
is_prime
|
||||
Sample prime ending in 1: 101
|
||||
Sample prime ending in 1: 401
|
||||
...
|
||||
168 primes in range of 1 to a thousand.
|
||||
|
||||
prime_factors
|
||||
prime factors of 12: 2 2 3
|
||||
prime factors of 13: 13
|
||||
prime factors of 876,512,779: 211 4154089
|
||||
|
||||
are_coprime
|
||||
Are 21 and 22 coprime? yes
|
||||
Are 21 and 24 coprime? no
|
||||
|
||||
goldbach
|
||||
Number must be > 2 and even: 11 is not.
|
||||
4 = 2 + 2
|
||||
6 = 3 + 3
|
||||
...
|
||||
32 = 3 + 29
|
||||
32 = 13 + 19
|
||||
...
|
||||
100 = 3 + 97
|
||||
100 = 11 + 89
|
||||
...
|
||||
```
|
||||
|
||||
对于 `goldbach` 函数,即使一个相当小的偶数值(例如18)也许存在多个一对质数之和的组合(在这种情况下,5+13和7+11)。因此存在多个质数对使得尝试证明哥德巴赫猜想变得复杂。
|
||||
|
||||
### 封装使用库的 Python 程序
|
||||
|
||||
Python 不同于 C,它不是一个静态编译语言,这意味着 Python 客户示例程序必须访问动态版本而非静态版本的质数库。为了能这样做,Python 中有众多的支持一个外部语言接口(简称FFI)的模块(标准中或第三方的),它们允许用一种语言编写的程序来调用另一种语言编写的程序。Python 中的 `ctypes` 是一个标准的和相对简单允许 Python 代码调用 C 函数的FFI。
|
||||
|
||||
任何 FFI 都面临挑战,因为其接口语言与调用语言不大可能会具有完全相同的数据类型。例如:primes 库使用 C 语言类型 `unsigned int`,而 Python 并不具有这种类型;因此 `ctypes` FFI 将 C 语言中的 `unsigned int` 类型映射为 Python 中的 `int` 类型。在 primes 库中发布的四个 `extern` C 函数中,有两个在具有显示 `ctypes` 配置的 Python 中会表现得更好。
|
||||
|
||||
C 函数 `prime_factors` 和 `goldbach` 返回 `void` 而不是返回一个具体类型,但是 `ctypes` 默认会将 C 语言中的 `void` 替换为 Python 语言中的 `int`。当从 Python 代码中调用时,这两个 C 函数会从栈中返回一个随机整数值(因此,该值无任何意义)。然而,`ctypes` 可以被配置为函数返回 `None` (Python 中为 null 类型)。下面是对 `prime_factors` 函数的配置:
|
||||
|
||||
```
|
||||
primes.prime_factors.restype = None
|
||||
```
|
||||
|
||||
可以用类似的语句处理 `goldbach` 函数。
|
||||
|
||||
下面的交互示例(在 Python3 中)展示了在 Python 客户程序和 primes 库之间的接口是简单明了的。
|
||||
|
||||
```python
|
||||
>>> from ctypes import cdll
|
||||
|
||||
>>> primes = cdll.LoadLibrary("libshprimes.so") ## logical name
|
||||
|
||||
>>> primes.is_prime(13)
|
||||
1
|
||||
>>> primes.is_prime(12)
|
||||
0
|
||||
|
||||
>>> primes.are_coprimes(8, 24)
|
||||
0
|
||||
>>> primes.are_coprimes(8, 25)
|
||||
1
|
||||
|
||||
>>> primes.prime_factors.restype = None
|
||||
>>> primes.goldbach.restype = None
|
||||
|
||||
>>> primes.prime_factors(72)
|
||||
2 2 2 3 3
|
||||
|
||||
>>> primes.goldbach(32)
|
||||
32 = 3 + 29
|
||||
32 = 13 + 19
|
||||
```
|
||||
|
||||
在 primes 库中的函数只使用一个简单的数据类型—`unsigned int`。如果这个 C 语言库使用复杂的类型如结构体,如果库函数传递和返回指向结构体的指针,那么一个 FFI 比 `ctypes` 更适合作为一个在 Python 语言和 C 语言 可迁移的接口。尽管如此,`ctypes` 示例展示了一个 Python 客户程序可以使用 C 语言编写的库。值得注意的是,用作科学计算的流行的 Numpy 库是用 C 语言,然后在高级 Python API 中公开。
|
||||
|
||||
简单的 primes 库和高级的 Numpy 库都是使用 C 语言编写以支持不同编程语言。几乎每一个语言都可以与 C 语言交互,同时通过 C 语言也可以和任何其他语言交互。Python 很容易和 C 语言交互,当 [Panama 项目](https://openjdk.java.net/projects/panama) 成为 Java Native Interface(JNI)一个替代品后,Java 语言和 C 语言交互也会变的很容易。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/linux-software-libraries
|
||||
|
||||
作者:[Marty Kalin][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[萌新阿岩](https://github.com/mengxinayan)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/mkalindepauledu
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/rh_003499_01_linux31x_cc.png?itok=Pvim4U-B (5 pengiuns floating on iceburg)
|
||||
[2]: https://en.wikipedia.org/wiki/Christian_Goldbach
|
||||
[3]: https://condor.depaul.edu/mkalin
|
||||
[4]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
|
||||
[5]: http://www.opengroup.org/onlinepubs/009695399/functions/sqrt.html
|
||||
[6]: https://openjdk.java.net/projects/panama
|
Loading…
Reference in New Issue
Block a user