2009-09-30 71 views
68

Tôi đang cố gắng trả về một chuỗi C từ một hàm nhưng nó không hoạt động. Đây là mã của tôi.Trả về chuỗi C từ một hàm

char myFunction() 
{ 
    return "My String"; 
} 

Trong chính tôi gọi nó như thế này:

int main() 
{ 
    printf("%s",myFunction()); 
} 

Tôi cũng đã thử một số cách khác để myFunction nhưng họ không làm việc. Ví dụ:

char myFunction() 
{ 
    char array[] = "my string"; 
    return array; 
} 

Lưu ý: Tôi không được phép sử dụng con trỏ!

Ít nền về vấn đề này: Có chức năng tìm ra tháng nào là ví dụ; nếu 1 của nó sau đó nó trở lại tháng một vv vv

Vì vậy, khi nó sẽ in, nó làm như thế này. printf("Month: %s",calculateMonth(month));. Bây giờ vấn đề là làm thế nào để trả về chuỗi đó từ hàm calculateMonth.

+8

Thật không may bạn * cần * con trỏ trong trường hợp này. –

+2

Tại sao bạn không thể sử dụng con trỏ? –

+0

(bạn cần phải trả lại một int trong chính bằng cách này) – user224579

Trả lời

148

chữ ký chức năng của bạn cần phải được:

const char * myFunction() 
{ 
    return "My String"; 
} 

Edit:

Bối cảnh:

Đó là năm kể từ khi bài này & không bao giờ nghĩ rằng nó sẽ được bầu lên, bởi vì nó rất cơ bản đối với C & C++. Tuy nhiên, cần thảo luận thêm một chút.

Trong C (& C++ cho vấn đề đó), một chuỗi chỉ là một mảng byte được kết thúc bằng byte không - do đó thuật ngữ "chuỗi-số không" được sử dụng để thể hiện hương vị đặc biệt của chuỗi. Có các loại dây khác, nhưng trong C (& C++), hương vị này vốn đã được hiểu bởi chính ngôn ngữ đó. Các ngôn ngữ khác (Java, Pascal, vv) sử dụng các phương pháp khác nhau để hiểu "chuỗi của tôi".

Nếu bạn đã từng sử dụng API Windows (trong C++), bạn sẽ thấy các thông số chức năng khá thường xuyên như: "LPCSTR lpszName". Phần 'sz' đại diện cho khái niệm 'chuỗi-số không' này: một mảng các byte có một terminator null (/ zero).

Làm rõ:

Vì mục đích của việc này 'giới thiệu', tôi sử dụng từ 'byte' và 'nhân vật' thay thế cho nhau, bởi vì nó dễ dàng hơn để học hỏi theo cách này. Lưu ý rằng có các phương thức khác (ký tự rộng và các hệ thống ký tự nhiều byte - mbcs) được sử dụng để đối phó với các ký tự quốc tế. UTF-8 là một ví dụ về một mbcs. Vì mục đích giới thiệu, tôi lặng lẽ 'bỏ qua' tất cả những điều này.

Memory:

Điều này có nghĩa là một chuỗi như "chuỗi của tôi" thực sự sử dụng 9 + 1 (= 10) byte. Điều này rất quan trọng để biết khi nào bạn cuối cùng cũng có được xung quanh để phân bổ chuỗi động. Vì vậy, nếu không có 'chấm dứt bằng không', bạn không có chuỗi. Bạn có một mảng ký tự (còn được gọi là bộ đệm) treo xung quanh trong bộ nhớ.

Tuổi thọ của dữ liệu:

Việc sử dụng các chức năng như sau:

const char * myFunction() 
{ 
    return "My String"; 
} 
int main() 
{ 
    const char* szSomeString = myFunction(); // fraught with problems 
    printf("%s", szSomeString); 
} 

... nói chung sẽ đất bạn với ngẫu nhiên unhandled-ngoại lệ/lỗi phân khúc và những thứ tương tự, đặc biệt là ' xuống đường '.

Tóm lại, mặc dù câu trả lời của tôi là đúng - 9 lần trong số 10 bạn sẽ kết thúc với một chương trình bị treo nếu bạn sử dụng theo cách đó, đặc biệt nếu bạn cho rằng đó là 'thực hành tốt' . Tóm lại: Nói chung là không.

Ví dụ: hãy tưởng tượng một thời gian trong tương lai, chuỗi hiện cần được thao tác theo một cách nào đó. Nói chung, một coder sẽ 'đi theo con đường dễ dàng' và (cố gắng) viết mã như thế này:

const char * myFunction(const char* name) 
{ 
    char szBuffer[255]; 
    snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name); 
    return szBuffer; 
} 

Đó là, chương trình của bạn sẽ sụp đổ vì trình biên dịch (có thể/không) đã phát hành bộ nhớ được sử dụng bởi szBuffer vào thời điểm printf() trong số main() được gọi. (Trình biên dịch của bạn cũng nên cảnh báo bạn về những vấn đề như vậy trước).

Có hai cách để trả lại các chuỗi không bị chặn dễ dàng.

  1. bộ đệm trả về (tĩnh hoặc được phân bổ động) tồn tại trong một thời gian. Trong C++ sử dụng 'lớp trợ giúp' (ví dụ: std::string) để xử lý tuổi thọ của dữ liệu (yêu cầu thay đổi giá trị trả về của hàm) hoặc
  2. chuyển bộ đệm đến hàm được điền thông tin.

Lưu ý rằng không thể sử dụng chuỗi mà không sử dụng con trỏ trong C. Như tôi đã chỉ ra, chúng đồng nghĩa. Ngay cả trong C++ với các lớp mẫu, luôn có bộ đệm (tức là con trỏ) đang được sử dụng trong nền.

Vì vậy, để trả lời tốt hơn (câu hỏi đã được sửa đổi). (chắc chắn có một loạt các 'câu trả lời khác' có thể được cung cấp).

Đáp Safer:

ví dụ 1. sử dụng chuỗi tĩnh được phân bổ:

const char* calculateMonth(int month) 
{ 
    static char* months[] = {"Jan", "Feb", "Mar" .... }; 
    static char badFood[] = "Unknown"; 
    if (month<1 || month>12) 
     return badFood; // choose whatever is appropriate for bad input. Crashing is never appropriate however. 
    else 
     return months[month-1]; 
} 
int main() 
{ 
    printf("%s", calculateMonth(2)); // prints "Feb" 
} 

gì 'tĩnh' không ở đây (nhiều lập trình viên không thích kiểu này 'phân bổ) là ở chỗ các chuỗi được đưa vào phân đoạn dữ liệu của chương trình. Đó là, nó được phân bổ vĩnh viễn.

Nếu bạn di chuyển qua C++ bạn sẽ sử dụng chiến lược tương tự:

class Foo 
{ 
    char _someData[12]; 
public: 
    const char* someFunction() const 
    { // the final 'const' is to let the compiler know that nothing is changed in the class when this function is called. 
     return _someData; 
    } 
} 

... nhưng nó có thể dễ dàng hơn để sử dụng các lớp helper như std::string, nếu bạn đang viết mã cho riêng bạn sử dụng (và không phải là một phần của thư viện được chia sẻ với người khác).

ví dụ: 2. sử dụng bộ đệm được người gọi xác định:

Đây là cách 'đánh lừa bằng chứng' để truyền các chuỗi xung quanh. Dữ liệu được trả về không bị thao tác bởi bên gọi điện. Đó là, ví dụ 1 có thể dễ dàng bị lạm dụng bởi một bên gọi điện thoại và cho bạn thấy lỗi ứng dụng. Bằng cách này, nó là an toàn hơn nhiều (mặc dù sử dụng nhiều dòng mã):

void calculateMonth(int month, char* pszMonth, int buffersize) 
{ 
    const char* months[] = {"Jan", "Feb", "Mar" .... }; // allocated dynamically during the function call. (Can be inefficient with a bad compiler) 
    if (!pszMonth || buffersize<1) 
     return; // bad input. Let junk deal with junk data. 
    if (month<1 || month>12) 
    { 
     *pszMonth = '\0'; // return an 'empty' string 
     // OR: strncpy(pszMonth, "Bad Month", buffersize-1); 
    } 
    else 
    { 
     strncpy(pszMonth, months[month-1], buffersize-1); 
    } 
    pszMonth[buffersize-1] = '\0'; // ensure a valid terminating zero! Many people forget this! 
} 

int main() 
{ 
    char month[16]; // 16 bytes allocated here on the stack. 
    calculateMonth(3, month, sizeof(month)); 
    printf("%s", month); // prints "Mar" 
} 

Có rất nhiều lý do tại sao phương pháp thứ 2 là tốt hơn, đặc biệt là nếu bạn đang viết một thư viện để được sử dụng bởi những người khác (bạn don 't cần phải khóa vào một giao thức phân bổ/deallocation cụ thể, bên thứ ba không thể phá vỡ mã của bạn, bạn không cần phải liên kết đến một thư viện quản lý bộ nhớ cụ thể), nhưng giống như tất cả các mã, nó thuộc vào bạn về những gì bạn thích tốt. Vì lý do đó, hầu hết mọi người lựa chọn ví dụ 1 cho đến khi họ đã bị đốt cháy rất nhiều lần rằng họ từ chối để viết nó theo cách đó nữa;)

từ chối trách nhiệm:

tôi nghỉ hưu vài năm trở lại và C của tôi bây giờ hơi bị gỉ. Mã trình diễn này nên biên dịch hoàn toàn với C (mặc dù vậy cũng không sao đối với bất kỳ trình biên dịch C++ nào).

+1

Thực ra, hàm cần trả về 'char *', vì các chuỗi ký tự trong C là kiểu 'char []'. Tuy nhiên, chúng không được sửa đổi theo bất kỳ cách nào, do đó trả về 'const char *' được ưu tiên hơn (xem https://www.securecoding.cert.org/confluence/x/mwAV). Trả về 'char *' có thể cần thiết nếu chuỗi sẽ được sử dụng trong một hàm thư viện kế thừa hoặc bên ngoài (không may) mong đợi một 'char *' làm đối số, thậm chí khó khăn nó sẽ chỉ đọc từ nó. C++, mặt khác, có chuỗi ký tự kiểu 'const char []' (và, kể từ C++ 11, bạn cũng có thể có 'std :: string' literals). – TManhente

+0

Một quan điểm: Tôi đã luôn luôn phát hiện 'My' ở bất kỳ dạng mã nào. Nó cho thấy thiếu suy nghĩ về những người sở hữu mã và không thêm bất kỳ thông tin nào về mã. Tôi đã luôn luôn nướng những người mới ngay từ uni người có thói quen đáng thương này. thậm chí 'Một số' tốt hơn 'My' – cmroanirgo

+15

@cmroanirgo tiền tố * my * tuyên bố cho người đọc rằng chức năng đã được tạo bởi người dùng. Tôi thấy nó hoàn toàn hợp lý để sử dụng trong bối cảnh như vậy. – arman

2

Loại trả về hàm của bạn là một char đơn. Bạn nên trả về một con trỏ tới phần tử đầu tiên của mảng ký tự. Nếu bạn không thể sử dụng con trỏ, sau đó bạn đang say. :(

+0

Tôi không được phép sử dụng con trỏ: @ đó chính là vấn đề. – itsaboutcode

7

Vấn đề của bạn là với kiểu trả về của hàm - nó phải là:

char *myFunction() 

... và sau đó xây dựng ban đầu của bạn sẽ làm việc

Lưu ý rằng bạn không thể có. Các chuỗi C không có con trỏ nào được tham gia, ở đâu đó dọc theo dòng.

Ngoài ra: Bật cảnh báo trình biên dịch, bạn nên cảnh báo bạn về dòng trả về đó chuyển đổi char * thành char không có dàn diễn viên rõ ràng.

+1

Tôi nghĩ rằng chữ ký nên const char * kể từ khi chuỗi là một chữ nhưng nếu tôi không nhầm lẫn trình biên dịch sẽ chấp nhận điều này. – Luke

1

A char chỉ là một ký tự một byte. Nó không thể lưu trữ chuỗi ký tự, cũng không phải là một con trỏ (mà bạn dường như không thể có). Do đó bạn không thể giải quyết vấn đề của mình mà không sử dụng con trỏ (trong đó char[] là cú pháp đường cho).

0

Nguyên mẫu chức năng của bạn cho biết hàm của bạn sẽ trả về một char. Vì vậy, bạn không thể trả về một chuỗi trong hàm của bạn.

11

Chuỗi C được định nghĩa là con trỏ đến một mảng ký tự.

Nếu bạn không thể có con trỏ, theo định nghĩa bạn không thể có chuỗi.

6

Lưu ý chức năng mới này:

const char* myFunction() 
{ 
     static char array[] = "my string"; 
     return array; 
} 

Tôi định nghĩa "mảng" như tĩnh, nếu không khi hàm kết thúc, biến (và con trỏ bạn đang trở về) được ra khỏi phạm vi. Vì bộ nhớ đó được cấp phát trên ngăn xếp, nó sẽ bị hỏng. Nhược điểm của việc thực hiện này là mã không được tái nhập và không phải là chủ đề an toàn.

Một giải pháp thay thế khác là sử dụng malloc để phân bổ chuỗi trong heap, sau đó miễn phí trên các vị trí chính xác của mã của bạn. Mã này sẽ được tái hấp dẫn và luồng an toàn.

EDIT:

Như đã đề cập trong các bình luận, đây là một thói quen rất xấu, vì kẻ tấn công sau đó có thể tiêm mã để ứng dụng của bạn (anh ta cần phải mở mã sử dụng gdb, sau đó đưa ra một breakpoint và sửa đổi giá trị của một biến trả về tràn và vui vẻ chỉ được bắt đầu).

Nếu được khuyến nghị nhiều hơn để cho người gọi xử lý về phân bổ bộ nhớ. Xem ví dụ mới này:

char* myFunction(char* output_str, size_t max_len) 
{ 
    const char *str = "my string"; 
    size_t l = strlen(str); 
    if (l+1 > max_len) { 
     return NULL; 
    } 
    strcpy(str, str, l); 
    return input; 
} 

Lưu ý rằng nội dung duy nhất có thể sửa đổi là nội dung người dùng. Một hiệu ứng phụ khác - mã này bây giờ là luồng an toàn, ít nhất là từ quan điểm của thư viện. Lập trình viên gọi phương thức này nên xác minh rằng phần bộ nhớ được sử dụng là luồng an toàn.

+1

Đây thường là một cách xấu để đi về mọi thứ. char * có thể được điều khiển bởi mã xung quanh. Tức là, bạn có thể làm những việc như thế này: strcpy (myFunction(), "Một chuỗi dài thực sự"); và chương trình của bạn sẽ bị lỗi do vi phạm quyền truy cập. – cmroanirgo

1

Nếu bạn thực sự không thể sử dụng con trỏ, làm một cái gì đó như thế này:

char get_string_char(int index) 
{ 
    static char array[] = "my string"; 
    return array[index]; 
} 

int main() 
{ 
    for (int i = 0; i < 9; ++i) 
     printf("%c", get_string_char(i)); 
    printf("\n"); 
    return 0; 
} 

Sự kỳ diệu số 9 là khủng khiếp, đây không phải là một ví dụ về lập trình tốt. Nhưng bạn sẽ có được điểm. Lưu ý rằng con trỏ và mảng là cùng một điều (kinda) vì vậy đây là một chút gian lận.

Hy vọng điều này sẽ hữu ích!

+2

Nó sử dụng một con trỏ trong quần áo của cừu! : P – Twisol

+1

Tôi đếm chính xác ba biểu thức con trỏ trong mã đó. – caf

+0

Thông thường nếu bạn cần phải thực hiện các giải pháp như vậy đối với vấn đề bài tập về nhà, thì các giả định sơ bộ của bạn là sai. – hrnt

5

Dựa trên cốt truyện mới được thêm vào của bạn với câu hỏi, tại sao không trả lại số nguyên từ 1 đến 12 trong tháng và để hàm main() sử dụng câu lệnh chuyển đổi hoặc bậc thang khác để quyết định nội dung cần in ? Nó chắc chắn không phải là cách tốt nhất để đi - char * sẽ được - nhưng trong bối cảnh của một lớp như thế này tôi tưởng tượng nó có lẽ là thanh lịch nhất.

+0

Bạn nói đúng, tôi cũng đang suy nghĩ về những điều khoản này. – itsaboutcode

2

Hoặc làm thế nào về vấn đề này một:

void print_month(int month) 
{ 
    switch (month) 
    { 
     case 0: 
      printf("january"); 
      break; 
     case 1: 
      printf("february"); 
      break; 
     ...etc... 
    } 
} 

Và gọi đó với tháng bạn tính toán ở một nơi khác.

Kính trọng,

Sebastiaan

+1

+1 không phải những gì OP hỏi nhưng đây có lẽ là nhiệm vụ mà bạn mong đợi, vì anh ấy không thể sử dụng con trỏ. –

+0

Ngay cả printf sử dụng con trỏ. Một con trỏ giống như một con dao - cần thiết cho cuộc sống và làm việc, nhưng bạn phải giữ nó bằng tay cầm và sử dụng mặt sắc nét để cắt với hoặc bạn sẽ có một thời gian xấu. Việc đặt không gian trống trong định nghĩa chức năng là lỗi não đối với nhiều lập trình viên C mới. char * func (char * s); char * func (char * s); char * func * char * s); là tất cả giống nhau nhưng tất cả trông khác nhau, và phức tạp lẫn lộn, * cũng là toán tử de-reference cho các biến là con trỏ. –

1

Vâng trong mã của bạn, bạn đang cố gắng để trả lại một String (Trong C đó là gì, nhưng null chấm dứt mảng các ký tự) nhưng kiểu trả về của hàm của bạn là char mà đang gây ra tất cả những rắc rối cho bạn. Thay vào đó bạn nên viết nó theo cách này:

 
const char* myFunction() 
{ 

    return "My String"; 

} 

Và nó luôn luôn tốt để đủ điều kiện loại của bạn với const khi gán literals trong C để con trỏ như literals trong C không thể thay đổi được.

3

Bạn có thể tạo mảng trong trình gọi, đó là hàm chính và chuyển mảng đó đến callee là myFunction() của bạn. Vì vậy myFunction có thể điền chuỗi vào mảng. Tuy nhiên bạn cần phải khai báo myFunction() như

char* myFunction(char * buf, int buf_len){ 
    strncpy(buf, "my string", buf_len); 
    return buf; 
} 

và trong hàm main, myFunction nên được gọi theo cách này

char array[51]; 
memset(array,0,51);/*all bytes are set to '\0'*/ 
printf("%s", myFunction(array,50));/*buf_len arguement is 50 not 51. This is to make sure the string in buf is always null-terminated(array[50] is always '\0')*/ 

Tuy nhiên con trỏ vẫn được sử dụng.

+0

Không thấy bất kỳ cách nào để thực hiện việc này mà không cần sử dụng con trỏ. – ouflak

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