簡體   English   中英

實現一個新的strcpy函數重新定義了庫函數strcpy?

[英]Implementing a new strcpy function redefines the library function strcpy?

據說我們可以編寫多個聲明但只能編寫一個定義。 現在,如果我使用相同的原型實現我自己的strcpy函數:

char * strcpy ( char * destination, const char * source );

那么我不是在重新定義現有的庫函數嗎? 這不應該顯示錯誤嗎? 或者它是否以某種方式與庫函數以目標代碼形式提供的事實有關?

編輯:在我的機器上運行以下代碼說“分段故障(核心轉儲)”。 我正在使用linux並且已經編譯而沒有使用任何標志。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *strcpy(char *destination, const char *source);

int main(){
    char *s = strcpy("a", "b");
    printf("\nThe function ran successfully\n");
    return 0;
}

char *strcpy(char *destination, const char *source){
    printf("in duplicate function strcpy");
    return "a";
}

請注意,我不是要嘗試實現該功能。 我只是想重新定義一個函數並詢問后果。

編輯2:在應用Mats建議的更改后,程序不再提供分段錯誤,盡管我仍在重新定義函數。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *strcpy(char *destination, const char *source);

int main(){
    char *s = strcpy("a", "b");
    printf("\nThe function ran successfully\n");
    return 0;
}

char *strcpy(char *destination, const char *source){
    printf("in duplicate function strcpy");
    return "a";
}

C11(ISO / IEC 9899:201x)§7.1.3 保留標識符

- 如果包含任何相關標頭,則保留以下任何子條款中的每個宏名稱(包括未來的庫方向)以供指定使用; 除非另有明確說明。

- 以下任何子條款中包含外部鏈接的所有標識符(包括未來的庫方向)始終保留用作具有外部鏈接的標識符。

- 如果包含任何相關標頭,則保留下列任何子條款(包括未來庫方向)中列出的具有文件范圍的每個標識符,以用作宏名稱和具有相同名稱空間的文件范圍的標識符。

如果程序在保留它的上下文中聲明或定義標識符,或者將保留標識符定義為宏名稱,則行為是未定義的。 請注意,這並不意味着你不能這樣做,正如這篇文章所示,它可以在gcc和glibc中完成。

glibc§1.3.3保留名稱證明了一個更明確的原因:

來自ISO C標准的所有庫類型,宏,變量和函數的名稱都是無條件保留的; 您的程序可能不會重新定義這些名稱。 如果您的程序明確包含定義或聲明它們的頭文件,則保留所有其他庫名。 這些限制有幾個原因:

如果您使用名為exit的函數執行與標准退出函數完全不同的操作,那么閱讀代碼的其他人可能會非常困惑。 防止這種情況有助於使您的程序更易於理解,並有助於模塊化和可維護性。

它避免了用戶意外重新定義其他庫函數調用的庫函數的可能性。 如果允許重新定義,那些其他功能將無法正常工作。

它允許編譯器在調用這些函數時進行任何特殊的優化,而不會被用戶重新定義。 一些庫設施,例如用於處理可變參數(參見Variadic函數)和非本地出口(參見非本地退出)的設施,實際上需要C編譯器方面的大量合作,並且相對於實現,編譯器可能更容易將它們視為語言的內置部分。

這幾乎肯定是因為你傳遞的是一個“字符串文字”的目的地。

char * s = strcpy(“a”,“b”);

隨着編譯器知道“我可以執行strcpy inline”,所以你的函數永遠不會被調用。

您正試圖在字符串文字"a"上復制"b" "a" ,但這不起作用。

做一個char a[2]; strcpy(a, "b"); 它會運行 - 它可能不會調用你的strcpy函數,因為即使你沒有可用的優化,編譯器也會內聯小的strcpy

將試圖修改不可修改的內存放在一邊,請記住,正式不允許重新定義標准庫函數。

但是,在某些實現中,您可能會注意到為標准庫函數提供另一個定義不會觸發通常的“多重定義”錯誤。 發生這種情況是因為在這種實現中,標准庫函數被定義為所謂的“弱符號”。 例如,GCC標准庫就是眾所周知的。

這樣做的直接后果是,當您使用外部鏈接定義自己的標准庫函數“版本”時,您的定義將覆蓋整個程序的“弱”標准定義。 您會注意到,不僅您的代碼現在調用您的函數版本,而且所有預編譯的[第三方]庫中的所有類也都會調度到您的定義中。 它旨在作為一項功能,但您必須注意它,以避免無意中“使用”此功能。

舉個例子,你可以在這里閱讀它

如何替換C標准庫函數?

該實現的這一特性不違反語言規范,因為它在未定義行為的未知區域內運行,不受任何標准要求的約束。

當然,使用某些標准庫函數的內在/內聯實現的調用不會受到重新定義的影響。

你的問題很容易引起誤解。

您看到的問題與重新實現庫函數無關。

您只是嘗試編寫不可寫內存,即存在字符串文字a內存。

簡單來說,下面的程序在我的機器上給出了一個分段錯誤(用gcc 4.7.3編譯,沒有標志):

#include <string.h>

int main(int argc, const char *argv[])
{
    strcpy("a", "b");
    return 0;
}

但是,如果您調用的是不寫不可寫內存的strcpy (您的)版本,為什么分段會strcpy 僅僅因為你的功能沒有被調用。

如果使用-S標志編譯代碼並查看編譯器為其生成的匯編代碼,則不會call strcpy (因為編譯器已“內聯”該調用,這是您可以進行的唯一相關調用從主要看,是對puts的調用)。

.file   "test.c"
    .section    .rodata
.LC0:
    .string "a"
    .align 8
.LC1:
    .string "\nThe function ran successfully"
    .text
    .globl  main
    .type   main, @function
main:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movw    $98, .LC0(%rip)
    movq    $.LC0, -8(%rbp)
    movl    $.LC1, %edi
    call    puts
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   main, .-main
    .section    .rodata
.LC2:
    .string "in duplicate function strcpy"
    .text
    .globl  strcpy
    .type   strcpy, @function
strcpy:
.LFB3:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movl    $.LC2, %edi
    movl    $0, %eax
    call    printf
    movl    $.LC0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE3:
    .size   strcpy, .-strcpy
    .ident  "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
    .

我覺得余浩的答案對此有很好的解釋,標准引用:

來自ISO C標准的所有庫類型,宏,變量和函數的名稱都是無條件保留的; 您的程序可能不會重新定義這些名稱。 如果您的程序明確包含定義或聲明它們的頭文件,則保留所有其他庫名。 這些限制有幾個原因:

[...]

它允許編譯器在調用這些函數時進行任何特殊的優化,而不會被用戶重新定義。

你的例子可以這樣運作:( 使用strdup

char *strcpy(char *destination, const char *source);

int main(){
    char *s = strcpy(strdup("a"), strdup("b"));
    printf("\nThe function ran successfully\n");
    return 0;
}

char *strcpy(char *destination, const char *source){
    printf("in duplicate function strcpy");
    return strdup("a");
}

輸出:

  in duplicate function strcpy
  The function ran successfully

解釋此規則的方法是,您不能在最終鏈接對象(可執行文件)中有多個函數定義。 因此,如果鏈接中包含的所有對象只有一個函數的定義,那么你就是好的。 記住這一點,請考慮以下方案。

  1. 假設您重新定義了某個庫中定義的函數somefunction()。 你的函數在main.c(main.o)中,在函數庫中,函數在一個名為someobject.o的對象中(在libray中)。 請記住,在最后一個鏈接中,鏈接器僅查找庫中未解析的符號。 因為somefunction()已經從main.o解析,所以鏈接器甚至不在庫中查找它,也不會引入someobject.o。 最后一個鏈接只有一個函數的定義,事情很好。
  2. 現在假設在someobject.o中定義了另一個符號anotherfunction(),你也恰好調用它。 鏈接器將嘗試從someobject.o解析anotherfunction(),並從庫中將其拉入,它將成為最終鏈接的一部分。 現在,在最終鏈接中有兩個somefunction()定義 - 一個來自main.o,另一個來自someobject.o,鏈接器將拋出錯誤。

我經常使用這個:

void my_strcpy(char *dest, char *src)
{
    int i;

    i = 0;
    while (src[i])
    {
        dest[i] = src[i];
        i++;
    }
    dest[i] = '\0';
}

你也可以通過修改一行來做strncpy

void my_strncpy(char *dest, char *src, int n)
{
    int i;

    i = 0;
    while (src[i] && i < n)
    {
        dest[i] = src[i];
        i++;
    }
    dest[i] = '\0';
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM