2011-12-22 47 views
14

Tôi có một hàm được cung cấp hai số nguyên và trả về một chuỗi. Ngay bây giờ tôi có điều này:Cách chính xác trả về một chuỗi từ một hàm

char* myfunc(int a, int b, int* len) 
{ 
    int retLen = ... 
    char* ret = malloc(retLen + 1); 

    if (len != NULL) 
    { 
     *len = retLen; 
    } 

    return ret; 
} 

Tuy nhiên, hầu hết các chức năng trong thư viện C có xu hướng làm điều gì đó giống như:

int myfunc(char* ret, int a, int b) 
{ 
    ... 

    return retLen; 
} 

Sau đó bạn có dự kiến ​​sẽ cấp phát bộ nhớ cho các chức năng để điền vào. Điều này cho phép bạn làm nhiều hơn một chút như chọn nơi chuỗi được phân bổ.

Trong trường hợp này mặc dù có một số toán cần thiết trong hàm để có độ dài và không có lý do để có bộ đệm có kích thước bất kỳ khác với số cần thiết. Không có giới hạn trên về kích thước của bộ đệm (không phải là một trong đó là hợp lý anyway).

Điều gì được coi là thực hành tốt khi trả về chuỗi có độ dài được tìm thấy động cho các yếu tố đầu vào?

Trả lời

7

Một mô hình tôi thấy trong các chương trình hạt nhân-mode là:

  1. Bạn gọi hàm một lần, với một số bộ nhớ được phân bổ nếu bạn tình cờ có một số có sẵn, hoặc null như param nếu bạn xảy ra để có none
  2. Nếu bạn có bộ nhớ được cấp phát và chức năng tìm thấy nó đủ để đặt kết quả trong bộ nhớ đó và trả lại OK
  3. Nếu bạn không có bộ nhớ truyền vào hoặc bộ nhớ được truyền quá ít, chức năng sẽ trả về ERROR_NOT_ENOUGH_MEMORY và đặt trong một tham số đầu ra bộ nhớ cần thiết.
    • Sau đó bạn phân bổ bộ nhớ cần thiết này và gọi hàm lại

mẫu:

int myfunc(
    __out char* output, 
    __in size_t given, 
    __out size_t needed_or_resulted, 
    extra params ... 
){ 
    ... implementation 
} 

Các needed_or_resulted cũng có thể được sử dụng để truyền bao nhiêu bộ nhớ được đã được sử dụng trong trường hợp thành công.

Để được sử dụng như:

int result = myfunc(output, given, needed_or_resulted, extra params ...); 
if(result == OK) { 
    // all ok, do what you need done with result of size "needed_or_resulted" on "output" 
} else if(result == ERROR_NOT_ENOUGH_MEMORY) { 
    output = malloc(needed ... 
    result = myfunc(output, given, needed_or_resulted, extra params ...); 
    if(result == OK) { 
     // all ok, do what you need done with result of size "needed_or_resulted" on "output" 
    } else if(result == ERROR_OTHER) { 
     // handle other possible errors 
    } else { 
     // handle unknown error 
    } 
} else if(result == ERROR_OTHER) { 
    // handle other possible errors 
} else { 
    // handle unknown error 
} 
+0

Tất nhiên, với mẫu này, bạn sẽ vượt qua độ dài cùng với con trỏ. Nó thậm chí có thể là một tham số đầu vào/đầu ra (được chuyển thành một 'size_t *'). – cHao

+0

Đúng vậy, tôi đã chỉnh sửa chữ ký. – clyfe

+0

Câu trả lời rất hay giúp ích rất nhiều, cảm ơn bạn. Vài câu hỏi: Tại sao điều này lại thích một hàm như 'size_t myfunc (đầu ra char *, size_t được đưa ra, ...)' có hiệu quả trả về 'needed_or_resulted' để người dùng so sánh để có được ý nghĩa riêng của họ để thành công? Ngoài ra, tại sao điều này lại ưu tiên cho hàm thứ hai như 'myfuncLength' có tính toán độ dài cần thiết không? – Matt

2

Bạn đúng về lý do tại sao chữ ký int myfunc(char* ret, int a, int b) được ưu tiên hơn. Nó thực sự giải thích một điều khác - tại sao chiều dài cần phải được trả lại (bộ đệm có kích thước tại MAX, vì vậy chúng ta thường cần thông báo cho người gọi về số lượng chúng ta đã thực sự sử dụng).

Khi bạn phân bổ chuỗi bên trong một hàm, bạn thường không trả về kích thước của chuỗi, vì strlen có thể được sử dụng để tìm ra. Hãy xem strdup để biết ví dụ về chức năng phân bổ chuỗi động. Vì vậy, tôi sẽ thay đổi chữ ký của chức năng của bạn thành

char* myfunc(int a, int b) { 
    ... 
} 
+0

Nếu tôi đọc câu hỏi đúng, không có 'MAX'. Hoặc nếu có, nó quá lớn để tạo bộ đệm với. – cHao

+0

@cHao Đúng, đây là hình ảnh 'MAX' tưởng tượng mà OP nói rằng anh ấy không muốn giới thiệu. Trong phần đầu của câu trả lời, tôi giải thích lý do tại sao chúng ta cần trả về độ dài khi có 'MAX', không gợi ý rằng OP nên có một. Phần thứ hai đề cập nhiều hơn với tình hình trong tầm tay. – dasblinkenlight

+0

Ngoài ra, loại này biến quy tắc về 'giải phóng' những gì bạn 'malloc' trên tai của nó. Bạn nên rõ ràng hơn về người sở hữu bộ nhớ trong tài liệu hướng dẫn cho chức năng này. – cHao

3

Cách thứ hai tốt hơn vì nó gợi ý người gọi chịu trách nhiệm giải phóng bộ nhớ. Nguyên nhân trước đây gây ra các vấn đề lớn nếu người gọi và callee sử dụng các triển khai malloc khác nhau (ví dụ: trên Windows, gỡ lỗi và phát hành thường sử dụng các mô hình bộ nhớ không tương thích).

1

tôi sẽ vượt qua một char * bằng cách tham khảo.Nếu hàm chạy thành công, phân bổ chuỗi và gán nó cho tham chiếu con trỏ và trả về độ dài của chuỗi. Nếu lỗi gặp phải, hãy đặt errno và trả về -1.

int myfunc(int a, int b, char ** str) 
{ 
    int retLen; 

    /* code to calculate string length required */ 

    if (!(str)) 
    { 
     errno = EINVAL; 
     return(-1); 
    }; 
    if (!(*str = malloc(retLen))) 
     return(-1); 

    /* calculate new value and store to string */ 

    return(retLen); 
} 
2

Thực hiện theo các giao diện của snprintf, một chức năng tiêu chuẩn với chính xác cùng một vấn đề:

size_t myfunc(char *s, size_t n, int a, int b); 

sản lượng byte ngoài n-1 sẽ bị loại bỏ thay vì ghi vào mảng, và một byte rỗng được viết vào cuối của byte thực sự được ghi vào mảng.

Sau khi hoàn tất thành công, hàm snprintf() sẽ trả về số byte sẽ được viết thành s có n đủ lớn trừ khi kết thúc null> byte.

Nếu giá trị của n là số không trên một cuộc gọi đến snprintf(), không có gì được bằng văn bản, số byte mà có thể đã được viết đã n được đủ lớn trừ null chấm dứt sẽ được trả lại, và s có thể là một con trỏ null.

sử dụng tiêu biểu:

size_t needed = myfunc(0, 0, a, b) + 1; 
char *buf = malloc(needed); 
if (buf) { 
    myfunc(buf, needed, a, b); 
} 

Bạn có thể bao gồm các byte nul trong đếm trở lại - nó làm cho mã gọi đơn giản hơn, mặc dù hơi ít quen thuộc với người sử dụng để chuẩn snprintf.

Nếu tính toán retLen là đáng kinh ngạc, có thể có một đối số cho một hàm tính toán nó khi nó tạo chuỗi và trả về bộ đệm được phân bổ có kích thước phù hợp (có thể là realloc ed trên đường đi). Nhưng tôi sẽ không bình thường thậm chí nghĩ về nó. Để thuận tiện cho những người dùng muốn phân bổ, chỉ cần đặt mã trên vào một hàm myfunc_alloc và không bao giờ nhớ rằng nó trùng lặp một chút công việc. Người dùng đã có bộ đệm có thể gọi trực tiếp myfunc như vậy:

if (myfunc(buf, bufsize, a, b) >= bufsize) { 
    printf("buffer too small, string (%s) has been truncated\n", buf); 
} 
Các vấn đề liên quan