2013-06-12 35 views
8

Tôi có một hàm chuỗi chấp nhận một con trỏ tới một chuỗi nguồn và trả về một con trỏ đến một chuỗi đích. Chức năng này hiện đang hoạt động, nhưng tôi lo lắng rằng tôi không tuân theo phương thức thực hành tốt nhất là malloc, realloc và miễn phí.thực hành tốt nhất để trả về một chuỗi có độ dài thay đổi trong c

Điều khác biệt về hàm của tôi là độ dài của chuỗi đích không giống như chuỗi nguồn, vì vậy realloc() phải được gọi bên trong hàm của tôi. Tôi biết từ nhìn vào tài liệu ...

http://www.cplusplus.com/reference/cstdlib/realloc/

rằng địa chỉ bộ nhớ có thể thay đổi sau khi realloc. Điều này có nghĩa là tôi không thể "vượt qua tham chiếu" như một lập trình viên C có thể cho các chức năng khác, tôi phải trả về con trỏ mới.

Vì vậy, các nguyên mẫu cho chức năng của tôi là:

//decode a uri encoded string 
char *net_uri_to_text(char *); 

Tôi không thích cách tôi đang làm điều đó bởi vì tôi phải giải phóng con trỏ sau khi chạy chức năng:

char * chr_output = net_uri_to_text("testing123%5a%5b%5cabc"); 
printf("%s\n", chr_output); //testing123Z[\abc 
free(chr_output); 

Có nghĩa là malloc() và realloc() được gọi bên trong hàm của tôi và hàm free() được gọi bên ngoài hàm của tôi.

Tôi có một nền tảng về ngôn ngữ cấp cao, (perl, plpgsql, bash) để bản năng của tôi là đóng gói thích hợp của những việc như vậy, nhưng điều đó có thể không phải là thực hành tốt nhất trong C.

Câu hỏi đặt ra: Liệu tôi cách thực hành tốt nhất, hoặc là có một cách tốt hơn tôi nên làm theo?

đầy đủ ví dụ

biên dịch và chạy với hai cảnh báo trên argc và argv đối số không sử dụng, bạn có thể yên tâm bỏ qua những cảnh báo hai.

example.c:

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

char *net_uri_to_text(char *); 

int main(int argc, char ** argv) { 
    char * chr_input = "testing123%5a%5b%5cabc"; 
    char * chr_output = net_uri_to_text(chr_input); 
    printf("%s\n", chr_output); 
    free(chr_output); 
    return 0; 
} 

//decodes uri-encoded string 
//send pointer to source string 
//return pointer to destination string 
//WARNING!! YOU MUST USE free(chr_result) AFTER YOU'RE DONE WITH IT OR YOU WILL GET A MEMORY LEAK! 
char *net_uri_to_text(char * chr_input) { 
    //define variables 
    int int_length = strlen(chr_input); 
    int int_new_length = int_length; 
    char * chr_output = malloc(int_length); 
    char * chr_output_working = chr_output; 
    char * chr_input_working = chr_input; 
    int int_output_working = 0; 
    unsigned int uint_hex_working; 
    //while not a null byte 
    while(*chr_input_working != '\0') { 
    //if % 
    if (*chr_input_working == *"%") { 
     //then put correct char in 
     sscanf(chr_input_working + 1, "%02x", &uint_hex_working); 
     *chr_output_working = (char)uint_hex_working; 
     //printf("special char:%c, %c, %d<\n", *chr_output_working, (char)uint_hex_working, uint_hex_working); 
     //realloc 
     chr_input_working++; 
     chr_input_working++; 
     int_new_length -= 2; 
     chr_output = realloc(chr_output, int_new_length); 
     //output working must be the new pointer plys how many chars we've done 
     chr_output_working = chr_output + int_output_working; 
    } else { 
     //put char in 
     *chr_output_working = *chr_input_working; 
    } 
    //increment pointers and number of chars in output working 
    chr_input_working++; 
    chr_output_working++; 
    int_output_working++; 
    } 
    //last null byte 
    *chr_output_working = '\0'; 
    return chr_output; 
} 
+8

Tôi thích phần '*"% "'. : D –

+0

Cảm ơn, tôi chỉ phát hiện ra rằng ''%'' hoạt động. :) – Michael

+0

Nếu bạn quấn mã của bạn giữa các dấu gạch chéo ngược ('), thì chúng sẽ được định dạng là mã –

Trả lời

8

Đó là hoàn toàn ok để trở malloc 'd đệm từ chức năng trong C, miễn là bạn có tài liệu thực tế là họ làm. Rất nhiều thư viện làm điều đó, mặc dù không có hàm nào trong thư viện chuẩn.

Nếu bạn có thể tính toán (không quá bi quan trên ràng buộc) số ký tự cần phải được ghi vào bộ đệm với giá rẻ, bạn có thể cung cấp chức năng thực hiện điều đó và cho phép người dùng gọi nó.

Cũng có thể, nhưng ít thuận tiện hơn, để chấp nhận bộ đệm cần điền; Tôi đã nhìn thấy một vài thư viện mà làm điều đó như sau:

/* 
* Decodes uri-encoded string encoded into buf of length len (including NUL). 
* Returns the number of characters written. If that number is less than len, 
* nothing is written and you should try again with a larger buffer. 
*/ 
size_t net_uri_to_text(char const *encoded, char *buf, size_t len) 
{ 
    size_t space_needed = 0; 

    while (decoding_needs_to_be_done()) { 
     // decode characters, but only write them to buf 
     // if it wouldn't overflow; 
     // increment space_needed regardless 
    } 
    return space_needed; 
} 

Bây giờ người gọi là chịu trách nhiệm về việc phân bổ, và sẽ làm một cái gì đó giống như

size_t len = SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH; 
char *result = xmalloc(len); 

len = net_uri_to_text(input, result, len); 
if (len > SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH) { 
    // try again 
    result = xrealloc(input, result, len); 
} 

(Ở đây, xmallocxrealloc được " an toàn "phân bổ chức năng mà tôi đã thực hiện để bỏ qua kiểm tra NULL.)

+1

+1 để đề cập đến tài liệu phân bổ dynmmic! -) – alk

+2

Hai khía cạnh tốt đẹp về việc mong người gọi vượt qua một bộ đệm (+ kích thước của nó): 1. Người gọi có thể biết chiều dài tối đa lên phía trước, vì vậy anh ta có thể quyết định sử dụng một mảng được phân bổ trên ngăn xếp. 2.) Quyền sở hữu bộ nhớ không được chuyển, tức là cả việc phân bổ và deallocation xảy ra trên trang người gọi - điều này rất quan trọng đối với Windows trong trường hợp người gọi nằm trong một DLL khác với callee (nó bị cấm cấp phát bộ nhớ trong một DLL và phát hành nó trong một phiên bản khác trên Windows, vì trình quản lý bộ nhớ là mỗi mô-đun, không phải cho mỗi quá trình). –

+0

@ FrerichRaabe: Tôi không biết về điều đó. Tôi biết rằng nó cho phép các trình quản lý bộ nhớ tùy chỉnh được sử dụng thay cho 'malloc', nó cũng hữu ích trên Unix. –

2

Hoàn toàn OK để trả lại giá trị mới malloc -ed (và có thể nội bộ realloc ed) từ các chức năng, bạn chỉ cần ghi lại rằng bạn đang làm như vậy (như bạn làm ở đây).

mục rõ ràng khác:

  • Thay vì int int_length bạn có thể muốn sử dụng size_t. Đây là "loại không dấu" (thường là unsigned int hoặc unsigned long) là loại thích hợp cho độ dài của chuỗi và đối số cho malloc.
  • Ban đầu, bạn cần phân bổ n + 1 byte, trong đó n là độ dài của chuỗi, vì strlen không bao gồm 0 byte kết thúc.
  • Bạn nên kiểm tra malloc không (trả lại NULL). Nếu chức năng của bạn vượt qua được lỗi, hãy ghi lại trong phần bình luận mô tả chức năng.
  • sscanf có trọng lượng khá nặng để chuyển đổi hai byte hex. Không phải sai, ngoại trừ việc bạn không kiểm tra xem chuyển đổi có thành công hay không (điều gì sẽ xảy ra nếu đầu vào không đúng định dạng? Bạn có thể quyết định đây là vấn đề của người gọi nhưng nói chung bạn có thể muốn xử lý). Bạn có thể sử dụng isxdigit từ <ctype.h> để kiểm tra các chữ số thập lục phân và/hoặc strtoul để thực hiện chuyển đổi.
  • Thay vì thực hiện một realloc cho mỗi chuyển đổi %, bạn có thể muốn thực hiện "thu gọn realloc cuối cùng" nếu muốn. Lưu ý rằng nếu bạn phân bổ (nói) 50 byte cho một chuỗi và tìm thấy nó chỉ yêu cầu 49 bao gồm 0 byte cuối cùng, nó có thể không có giá trị làm một realloc sau khi tất cả.
2

Điều này là C đủ cấp thấp để buộc người lập trình có quyền quản lý bộ nhớ của mình. Đặc biệt, không có gì sai khi trả về một chuỗi atit malloc(). Đó là một thành ngữ phổ biến để trả về những lời chê bai cố định và có người gọi free() chúng.

Và dù sao, nếu bạn không thích phương pháp này, bạn luôn có thể lấy con trỏ tới chuỗi và sửa đổi nó từ bên trong hàm (sau lần sử dụng cuối cùng, nó vẫn cần phải là free() d).

Một điều, tuy nhiên, tôi không nghĩ là cần thiết là thu hẹp rõ ràng chuỗi. Nếu chuỗi mới ngắn hơn cũ, rõ ràng là đủ chỗ cho nó trong phần bộ nhớ của chuỗi cũ, vì vậy bạn không cần phải realloc().

(Ngoài thực tế là bạn quên bố trí thêm một byte cho ký tự NUL chấm dứt, tất nhiên ...)

Và, như mọi khi, bạn có thể chỉ cần trả về một con trỏ khác nhau mỗi khi hàm là được gọi và thậm chí bạn không cần phải gọi số realloc().

Nếu bạn chấp nhận một lời khuyên tốt nhất: bạn nên const -qualify chuỗi đầu vào của mình, để người gọi có thể đảm bảo rằng bạn không sửa đổi chúng. Sử dụng phương pháp này, bạn có thể gọi hàm một cách an toàn trên chuỗi ký tự chuỗi.

Tất cả trong tất cả, tôi muốn viết lại chức năng của bạn như thế này:

char *unescape(const char *s) 
{ 
    size_t l = strlen(s); 
    char *p = malloc(l + 1), *r = p; 

    while (*s) { 
     if (*s == '%') { 
      char buf[3] = { s[1], s[2], 0 }; 
      *p++ = strtol(buf, NULL, 16); // yes, I prefer this over scanf() 
      s += 3; 
     } else { 
      *p++ = *s++; 
     } 
    } 

    *p = 0; 
    return r; 
} 

Và gọi nó là như sau:

int main() 
{ 
    const char *in = "testing123%5a%5b%5cabc"; 
    char *out = unescape(in); 
    printf("%s\n", out); 
    free(out); 

    return 0; 
} 
+0

Welp, tôi đã không đề cập đến 'size_t' và' strtol() 'một cách rõ ràng, và tôi cũng giả định rằng' malloc() 'không bao giờ thất bại ... Xem ra! –

0

tôi sẽ tiếp cận vấn đề theo một cách hơi khác nhau. Cá nhân, tôi sẽ chia chức năng của bạn thành hai.Hàm đầu tiên để tính toán kích thước bạn cần để malloc. Thứ hai sẽ viết chuỗi đầu ra cho con trỏ đã cho (con trỏ đã được cấp phát bên ngoài hàm). Điều đó tiết kiệm một số cuộc gọi đến realloc, và sẽ giữ sự phức tạp như nhau. Một chức năng có thể để tìm ra kích thước của chuỗi mới là:

int getNewSize (char *string) { 
    char *i = string; 
    int size = 0, percent = 0; 
    for (i, size; *i != '\0'; i++, size++) { 
     if (*i == '%') 
      percent++; 
    } 
    return size - percent * 2; 
} 

Tuy nhiên, như đã đề cập trong câu trả lời khác không có vấn đề trong việc trả lại một bộ đệm malloc'ed miễn là bạn ghi lại nó!

+0

Lưu ý rằng nếu bạn quyết định tái cấu trúc như thế này, bạn có thể có "chức năng tính toán không gian" * cũng * xác minh rằng URL được tạo đúng (không có những thứ như% -! Ở giữa).Đôi khi mọi người cũng tìm cách tiếp cận lai: xác minh, tùy chọn malloc, tùy chọn chuyển đổi thành bộ đệm (cho dù người dùng được cung cấp hoặc malloc-ed), trả về bó thông tin (thông qua 'struct' hoặc con trỏ do người gọi cung cấp), v.v. – torek

0

Ngoài những gì đã được đề cập trong các bài đăng khác, bạn cũng nên ghi lại thực tế là chuỗi được phân bổ lại. Nếu mã của bạn được gọi bằng một chuỗi tĩnh hoặc một chuỗi được phân bổ với alloca, bạn không thể phân bổ lại mã đó.

0

Tôi nghĩ bạn có quyền quan tâm đến việc chia nhỏ mallocs và giải phóng. Như một quy luật, bất cứ điều gì làm cho nó, sở hữu nó và nên giải phóng nó.

Trong trường hợp này, nơi các chuỗi là tương đối nhỏ, một thủ tục tốt là làm cho bộ đệm chuỗi lớn hơn bất kỳ chuỗi ký tự nào có thể chứa. Ví dụ: URL có giới hạn khoảng 2000 ký tự, vì vậy nếu bạn có 10.000 ký tự, bạn có thể lưu trữ bất kỳ URL nào có thể.

Bí quyết khác là lưu trữ cả chiều dài và dung lượng của chuỗi ở phía trước, để (int)*mystring == length of string(int)*(mystring + 4) == capacity của chuỗi. Do đó, chuỗi chỉ bắt đầu ở vị trí thứ 8 *(mystring+8). Bằng cách này, bạn có thể truyền xung quanh một con trỏ tới một chuỗi và luôn biết nó dài bao nhiêu và dung lượng bộ nhớ của chuỗi. Bạn có thể tạo các macro tự động tạo ra các offset này và tạo "mã đẹp".

Giá trị sử dụng bộ đệm theo cách này là bạn không cần phải thực hiện phân bổ lại. Giá trị mới sẽ ghi đè giá trị cũ và bạn cập nhật độ dài ở đầu chuỗi.

+1

Tôi nghĩ rằng 'kích thước mã hóa của chuỗi trong kỹ thuật vài byte đầu tiên là rất khó chịu, vì nó khá phổ biến (tôi chưa bao giờ thấy nó trong thực tế) và trình biên dịch không thể giúp nếu bạn quên mất thực tế này. Vì vậy, nếu bạn có một chuỗi "Hello", in nó có thể chỉ nhận được một ký tự đơn được in vì hầu hết các byte của int ban đầu là số không. Không phải là một điều tốt đẹp để gỡ lỗi. : -/ –

+0

Khi bạn sử dụng nội dung của chuỗi, bạn gọi nó là '* (mystring + 8)'. Ví dụ: printf ("% s \ n", * (mystring + 8)); một macro có thể được sử dụng nếu muốn. Thay vào đó là định nghĩa một cấu trúc thay vào đó, nhưng sau đó bạn phải xử lý các con trỏ lồng nhau. Theo kinh nghiệm của tôi, dễ sử dụng phương pháp tôi mô tả hơn là sử dụng con trỏ lồng nhau khi thao tác các chuỗi ngắn. –

Các vấn đề liên quan