gcc/g++사용자 정의 동명 함수 로 C 라 이브 러 리 함수 덮어 쓰기

4897 단어 c
머리말
사실 이 문 제 는 이전에 생각해 본 적 이 있 는데,매번 끝까지 따 지지 않 았 다.그 이 유 는 어떤 Linux C 프로 그래 밍 책 이 든 기본적으로 신뢰 할 수 있 는 의미 의 signal 함 수 를 사용 하여 해당 하 는 라 이브 러 리 함 수 를 덮어 쓰기 때문이다.예 를 들 어 에서 다음 과 같이 정의 되 었 다.SIGALRM이외 의 신호 가 중 단 된 시스템 호출 을 자동 으로 재 부팅 하고 다른 신 호 를 막 지 않 는 다.(신호 마스크 가 비어 있 지만 POSIX 는 포 획 된 신호 가 신호 처리 함수 가 실행 되 는 동안 항상 차단 되 는 것 을 보증 합 니 다)그러나 책 에서 라 이브 러 리 함 수 를 어떻게 덮어 쓰 는 지 에 대한 정 의 는 언급 되 지 않 았 습 니 다.서로 다른 컴 파일 러 에 있어 서 방법 이 다 르 기 때문에 여 기 는gcc에 만 있 습 니 다.
정적 링크 VS 동적 링크
주:결론 을 직접 보고 싶 으 면 본 부분의 내용 을 무시 할 수 있 습 니 다.
쉽게 말 하면 링크 는 대상 파일 을 다시 찾 을 수 있 는 최종 실행 가능 한 대상 파일 로 조합 하 는 것 입 니 다(다음은 모두'프로그램'이라는 단어 로 대체 합 니 다).대상 파일 에 기호 표 가 있 는데 그 중에서 해석 되 지 않 은 기호 참조 가 있 습 니 다.예 를 들 어 원본 파일 에서 함 수 를 설명 하 였 으 나 구체 적 인 정 의 를 내리 지 않 았 습 니 다.이 때 링크 기 는 다른 대상 파일 에서 해당 하 는 기호 정의 가 있 는 지 찾 습 니 다.
예 를 들 어 다음 원본 파일 이 있 습 니 다.
// main.c
void foo();
int main() {
    foo();
    return 0;
}
main.c에는foo성명 만 포함 되 어 있 고 정의 가 없 기 때문에 main.c 를 직접 컴 파일 하면 오류 가 발생 할 수 있 습 니 다.foo.c컴 파일 된 정적 라 이브 러 리libfoo.a를 제공 하면(컴 파일 과정 은 다음 과 같다)
// foo.c
#include 
void foo() { puts("foo"); }
$ gcc -c foo.c 
$ ar -rcs libfoo.a foo.o

그러면 링크 를 진행 할 수 있 습 니 다.gcc 컴 파일 과정 은 다음 과 같 습 니 다.
$ gcc main.c libfoo.a

이 과정 에서 먼저 소스 코드main.c를 컴 파일 하여 대상 파일 을 다시 찾 을 수 있 습 니 다.그 중에서 기호 표 에는 해석 되 지 않 은 기호 참조foo가 포함 되 어 있 습 니 다.이 때 링크 기 가 기록 한 다음 에 뒤의 대상 파일(정적 라 이브 러 리)에서foo이 포함 되 어 있 는 지 확인 하고 찾 으 면 일치 하 며 정 의 를 찾 지 않 습 니 다.
예 를 들 어 현재 다른 정 의 를 내 렸 습 니 다foo함수 의 라 이브 러 리libfoo2.a.소스 코드 는 다음 과 같 고 컴 파일 과정 은 같 습 니 다libfoo.a.
// foo2.c
#include 
void foo() { puts("foo2"); }

현재 각각 다른 순서에 따라 링크,실행 프로그램,관찰 결 과 를 진행 합 니 다.
$ gcc main.c libfoo.a libfoo2.a 
$ ./a.out 
foo
$ gcc main.c libfoo2.a libfoo.a 
$ ./a.out 
foo2

방금 의 결론 을 입증 하 였 으 며,어떤 뒤의 행위 도 앞의 행 위 를 덮어 쓰 지 않 았 다.
OK,그럼 문제 가 생 겼 습 니 다.stdio.h에는puts함수 의 성명 만 있 고 정의 가 없습니다.이것 이 바로 동적 라 이브 러 리 입 니 다.ldd명령 으로 프로그램 이 호출 한 동적 라 이브 러 리 를 볼 수 있 습 니 다.
$ ldd a.out 
    linux-vdso.so.1 =>  (0x00007fff78b02000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe770f5a000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fe771324000)
libc.so.6즉,C 표준 라 이브 러 리(동적 라 이브 러 리)는 특정한 디 렉 터 리 에 두 고 gcc 의-l 옵션 을 통 해 링크 의 동적 라 이브 러 리 를 지정 합 니 다.기호 정의 의 구체 적 인 내용 은 최종 프로그램 에 넣 지 않 고 기호 정의 가 있 는 동적 라 이브 러 리 경 로 를 기록 하여 프로그램 이 실 행 될 때 찾 습 니 다.프로그램의 부 피 를 간소화 한 것 이 장점 이 며,동적 링크 의 함 수 를 처음 호출 할 때 시간 이 걸 리 는 것 이 단점 이다.
링크 할 때 C 표준 라 이브 러 리 는 추가 옵션 없 이 동적 링크 를 할 수 있 습 니 다.-static옵션 을 추가 할 때 만 동적 링크 를 하지 않 고 C 표준 라 이브 러 리 의 정적 라 이브 러 리 를 정적 으로 연결 합 니 다.
더 자세 한 부분 은(즉 CSAPP)제7 장 을 참고 할 수 있다.
라 이브 러 리 함 수 는 일반적으로 동적 링크 를 진행 합 니 다.
라 이브 러 리 함 수 를 어떻게 덮어 씁 니까?
gcc 옵션no-builtin을 사용 하면 gcc 의 manpage 에서 관련 설명(여기 붙 이지 않 음)을 볼 수 있 습 니 다.대체적으로 gcc 는 일부 내장 함수 에 대해 바 텀 최적화 가 있 고 자신 이 같은 기능 을 실현 하 는 것 보다 부피 가 작고 속도 가 빠 른 바 텀 코드 를 만 들 수 있 습 니 다.이 옵션 을 사용 하면 기본적으로 시스템 의 최적화 함 수 를 사용 하지 않 고 사용자 정의 함 수 를 사용 합 니 다.
예 를 들 어 우 리 는 printf 를 정의 하 는 데 서 왔 다.
// printf.c
#include 
#include 

int printf(const char* format, ...) {
    write(STDOUT_FILENO, "my printf
", 10); write(STDOUT_FILENO, format, strlen(format)); return 0; } // main.c #include int main() { printf("hello
"); return 0; }

서로 다른 컴 파일 방식 의 결 과 를 관찰 하 다.
$ gcc -c printf.c 
$ gcc main.c printf.o -fno-builtin
$ ./a.out 
my printf
hello
$ gcc main.c printf.o
$ ./a.out 
hello
signal와 같은 최적화 되 지 않 은 함수(시스템 호출 포장 일 뿐)에 대해 직접 정적 링크 를 하면 된다.
// signal.c
#include 
#include   //   signal        sigaction   

typedef void Sigfunc(int);

Sigfunc* signal(int signo, Sigfunc* func) {
    printf("%d
", signo); return func; } // main.c #include int main() { signal(SIGINT, SIG_DFL); return 0; } $ gcc -c signal.c $ gcc main.c signal.o $ ./a.out 2

또한 매크로 정의 방식 으로 라 이브 러 리 함 수 를 교체 할 수 있 습 니 다.예 를 들 어
#define printf my_printf
int my_printf(const char* format, ...)
{
    //     
}

그러나 이러한 방법 을 추천 하지 않 습 니 다.매크로 교 체 는 컴 파일 하기 전에 이 루어 졌 기 때문에 최종 프로그램의 기호 정 보 는printf이 아니 라my_printf이 고stdio.h에서printf에 대한 성명 도 의 미 를 잃 었 습 니 다.실제 호출 된 것 은my_printf이기 때 문 입 니 다.
이전 방법 을 사용 하면 기 존 코드 를 수정 하지 않 아 도 되 는 토대 에서 라 이브 러 리 함수 에 대한 재 작성 버 전 을 호출 할 수 있 습 니 다.

좋은 웹페이지 즐겨찾기