2012-01-16 56 views
8

Tôi đang cố gắng học C và tôi rất bối rối.Chức năng C cụ thể hoạt động như thế nào?

Trong ngôn ngữ OOP tôi đã sử dụng, có khả năng thực hiện quá tải phương thức, trong đó cùng một chức năng có thể có các loại tham số khác nhau và gọi tùy chọn nào phù hợp nhất.

Bây giờ trong C tôi biết rằng đây không phải là trường hợp vì vậy tôi không thể tìm ra vấn đề sau, Cách printf() hoạt động.

Ví dụ:

char chVar = 'A'; 
int intVar = 123; 
float flVar = 99.999; 

printf("%c - %i - %f \n",chVar, intVar, flVar); 
printf("%i - %f - %c \n",intVar, flVar, chVar); 
printf("%f - %c - %i \n",flVar, chVar, intVar); 

Bây giờ là C does'nt chức năng hỗ trợ quá tải, như thế nào printf quản lý thực hiện bất kỳ số lượng các đối số, của bất kỳ loại, và sau đó làm việc một cách chính xác với họ?

Tôi đã cố gắng tìm printf() hoạt động bằng cách tải xuống gói nguồn glibc nhưng có vẻ khá có thể tìm thấy nó, mặc dù tôi sẽ tiếp tục tìm kiếm.

Có ai ở đây giải thích cách C thực hiện tác vụ trên không?

+0

Nếu bạn muốn biết thêm, hãy đọc các quy ước gọi và ngăn xếp –

+6

Câu hỏi của bạn là về C, nhưng bạn nói "Tôi đang cố gắng học C++." Nếu bạn đang cố gắng tìm hiểu C++, tốt nhất là bắt đầu bằng cách tránh các phần C của ngôn ngữ (và ở mức cao hơn của trừu tượng mà C++ dành), sau đó một khi bạn cảm thấy thoải mái với điều đó, để đào sâu vào các phần khác của ngôn ngữ . –

+0

Nếu bạn không muốn phải lo lắng về điều này thì hãy chuyển '-Wformat' làm đối số cho g ++. Các trình biên dịch khác nên, tôi nghĩ/hy vọng/mong đợi, có cảnh báo tương tự. Điều này sẽ làm cho trình biên dịch kiểm tra xem các loại có phù hợp hay không. –

Trả lời

11

C hỗ trợ loại chữ ký hàm được gọi là "varargs" có nghĩa là "đối số biến (số lượng)". Hàm này phải có ít nhất một đối số bắt buộc. Trong trường hợp printf, chuỗi định dạng là đối số bắt buộc.

Nói chung, trên máy dựa trên ngăn xếp, khi bạn gọi bất kỳ hàm C nào, các đối số được đẩy lên ngăn xếp từ phải sang trái. Theo cách này, đối số đầu tiên của hàm được tìm thấy trên "đầu" của ngăn xếp, ngay sau địa chỉ trả về.

Có các macro C được xác định cho phép bạn truy lục đối số biến.

Những điểm chính là:

  • Không có loại an toàn cho các đối số biến. Trong trường hợp printf(), nếu chuỗi định dạng sai, mã sẽ đọc kết quả không hợp lệ từ bộ nhớ, có thể bị lỗi.
  • Đối số biến được đọc qua một con trỏ được tăng lên thông qua bộ nhớ chứa các đối số đó.
  • Con trỏ đối số phải được khởi tạo với va_start, tăng lên với va_arg và được phát hành với va_end.

Tôi đã đăng tải một tấn mã bạn có thể tìm thấy thú vị về vấn đề có liên quan:

Best Way to Store a va_list for Later Use in C/C++

Dưới đây là một bộ xương của một printf() mà chỉ định dạng số nguyên ("% d"):

int printf(const char * fmt, ...) 
{ 
    int d; /* Used to store any int arguments. */ 
    va_list args; /* Used as a pointer to the next variable argument. */ 

    va_start(args, fmt); /* Initialize the pointer to arguments. */ 

    while (*fmt) 
    { 
     if ('%' == *fmt) 
     { 
      fmt ++; 

      switch (*fmt) 
      { 
       case 'd': /* Format string says 'd'. */ 
          /* ASSUME there is an integer at the args pointer. */ 

        d = va_arg(args, int); 
        /* Print the integer stored in d... */ 
        break; 
      } 
     } 
     else 
      /* Not a format character, copy it to output. */ 
     fmt++; 
    } 

    va_end(args); 
} 
4

Nội bộ, printf sẽ (ít nhất là thường) sử dụng một số macro từ stdarg.h. Ý tưởng chung là (một phiên bản mở rộng đáng kể của) một cái gì đó như thế này:

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

int my_vfprintf(FILE *file, char const *fmt, va_list arg) { 

    int int_temp; 
    char char_temp; 
    char *string_temp; 
    char ch; 
    int length = 0; 

    char buffer[512]; 

    while (ch = *fmt++) { 
     if ('%' == ch) { 
      switch (ch = *fmt++) { 
       /* %% - print out a single % */ 
       case '%': 
        fputc('%', file); 
        length++; 
        break; 

       /* %c: print out a character */ 
       case 'c': 
        char_temp = va_arg(arg, int); 
        fputc(char_temp, file); 
        length++; 
        break; 

       /* %s: print out a string  */ 
       case 's': 
        string_temp = va_arg(arg, char *); 
        fputs(string_temp, file); 
        length += strlen(string_temp); 
        break; 

       /* %d: print out an int   */ 
       case 'd': 
        int_temp = va_arg(arg, int); 
        itoa(int_temp, buffer, 10); 
        fputs(buffer, file); 
        length += strlen(buffer); 
        break; 

       /* %x: print out an int in hex */ 
       case 'x': 
        int_temp = va_arg(arg, int); 
        itoa(int_temp, buffer, 16); 
        fputs(buffer, file); 
        length += strlen(buffer); 
        break; 
      } 
     } 
     else { 
      putc(ch, file); 
      length++; 
     } 
    } 
    return length; 
} 

int my_printf(char const *fmt, ...) { 
    va_list arg; 
    int length; 

    va_start(arg, fmt); 
    length = my_vfprintf(stdout, fmt, arg); 
    va_end(arg); 
    return length; 
} 

int my_fprintf(FILE *file, char const *fmt, ...) { 
    va_list arg; 
    int length; 

    va_start(arg, fmt); 
    length = my_vfprintf(file, fmt, arg); 
    va_end(arg); 
    return length; 
} 


#ifdef TEST 

int main() { 
    my_printf("%s", "Some string"); 
    return 0; 
} 

#endif 

cho thịt nó ra không liên quan đến khá nhiều công việc - đối phó với chiều rộng lĩnh vực, độ chính xác, nhiều chuyển đổi, vv Điều này là đủ, tuy nhiên, ít nhất hãy đưa ra một hương vị về cách bạn truy xuất các đối số khác nhau của các loại khác nhau bên trong hàm của bạn.

+0

Đây là sai .. –

+0

@HeathHunnicutt: có lẽ bạn có thể giải thích những gì bạn nghĩ là sai? –

+0

Bạn đã bỏ qua phần phía trước nơi một va_list đã được tạo. Đó không phải là tự động, nhưng câu trả lời của bạn sẽ làm cho nó có vẻ như vậy. Nó có thể là (có lẽ không, nhưng có thể tôi cấp cho bạn), printf() là một wrapper quanh my_vprintf() như bạn đã đưa ra ví dụ. Nhưng kiến ​​thức quan trọng là làm thế nào để tạo một va_list, sử dụng va_start và đối số không biến đổi cuối cùng. Bỏ qua phần đó không phải là câu trả lời theo thứ tự "sai". –

2

(Đừng quên rằng, nếu bạn đang sử dụng gcc (và g ++?), Bạn có thể vượt qua -Wformat trong các tùy chọn trình biên dịch để trình biên dịch kiểm tra các loại đối số khớp với định dạng. các trình biên dịch có các tùy chọn tương tự.)

Có ai ở đây giải thích cách C thực hiện nhiệm vụ trên không?

Tín ngưỡng mù quáng. Nó giả định rằng bạn đã đảm bảo rằng các kiểu đối số khớp hoàn toàn với các chữ cái tương ứng trong chuỗi định dạng của bạn. Khi printf được gọi, tất cả các đối số được biểu diễn theo dạng nhị phân, không liên kết lẫn nhau với nhau, và được truyền hiệu quả như một đối số lớn duy nhất cho printf. Nếu chúng không khớp, bạn sẽ gặp vấn đề. Khi printf lặp qua chuỗi định dạng, mỗi khi nó thấy %d nó sẽ mất 4 byte từ các đối số (giả sử 32 bit, nó sẽ là 8 byte cho 64-bit ints của khóa học) và nó sẽ giải thích chúng như một số nguyên.

Bây giờ có thể bạn thực sự đã vượt qua double (thường chiếm gấp đôi bộ nhớ nhiều nhất là int), trong trường hợp này printf sẽ chỉ lấy 32 trong số các bit đó và biểu thị chúng dưới dạng số nguyên. Sau đó, trường định dạng tiếp theo (có thể là %d) sẽ thực hiện phần còn lại của số double.

Vì vậy, về cơ bản, nếu các loại không khớp hoàn hảo, bạn sẽ nhận được dữ liệu bị cắt xén. Và nếu bạn không may mắn, bạn sẽ có hành vi không xác định.

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