Tại sao chúng ta phải nói với printf () loại dữ liệu trong C?


8

Hãy xem xét mã C này:

#include <stdio.h>

main()
{
  int x=5;
  printf("x is ");
  printf("%d",5);
}

Trong phần này, khi chúng tôi viết, int x=5;chúng tôi đã nói với máy tính đó xlà một số nguyên. Máy tính phải nhớ đó xlà một số nguyên. Nhưng khi chúng ta xuất giá trị của xin, printf()chúng ta lại phải nói với máy tính đó xlà một số nguyên. Tại sao vậy?

Tại sao máy tính quên đó xlà một số nguyên?


5
Trình biên dịch biết rằng x là một số nguyên, nhưng printf thì không.
luiscubal

đó chỉ là cách thức hoạt động của quy ước gọi điện, chỉ có 1 lần thực hiện printf(char*, ...)và nó chỉ nhận được (một lượng tương đương) một con trỏ tới một bộ sưu tập dữ liệu
ratchet freak

@ratchetfreak Tại sao lại như vậy? Tại sao printf không tự động lấy con trỏ của kiểu dữ liệu như trong C ++ khi chức năng "cout" tự động biết kiểu dữ liệu?
dùng106313

5
Bạn đã xem xét printf("x is %x in hex, and %d in decimal and %o as octal",x,x,x);chưa?

1
Hãy thử với số 163. Các giá trị nhỏ hơn 8 khá không thú vị trong tình huống này. Hoặc tốt hơn nữa, lặp từ 0 đến 255 và xem các con số là gì. Vấn đề là có nhiều printf hơn là họ gõ.

Câu trả lời:


18

Có hai vấn đề đang diễn ra ở đây:

Vấn đề # 1: C là một ngôn ngữ gõ tĩnh ; tất cả các thông tin loại được xác định tại thời gian biên dịch. Không có thông tin loại được lưu trữ với bất kỳ đối tượng nào trong bộ nhớ sao cho loại và kích thước của nó có thể được xác định tại thời điểm chạy 1 . Nếu bạn kiểm tra bộ nhớ tại bất kỳ địa chỉ cụ thể nào trong khi chương trình đang chạy, tất cả những gì bạn sẽ thấy là bùn của byte; không có gì để cho bạn biết liệu địa chỉ cụ thể đó có thực sự chứa một đối tượng hay không, loại hoặc kích thước của đối tượng đó là gì hoặc làm thế nào để giải thích các byte đó (dưới dạng số nguyên, hoặc loại dấu phẩy động hoặc chuỗi ký tự trong chuỗi, v.v. ). Tất cả thông tin đó được đưa vào mã máy khi mã được biên dịch, dựa trên thông tin loại được chỉ định trong mã nguồn; ví dụ: định nghĩa hàm

void foo( int x, double y, char *z )
{
  ...
}

báo cho trình biên dịch tạo mã máy thích hợp để xử lý xnhư một số nguyên, ydưới dạng giá trị dấu phẩy động và znhư một con trỏ tới char. Lưu ý rằng bất kỳ sự không phù hợp nào về số lượng hoặc loại đối số giữa lệnh gọi hàm và định nghĩa hàm chỉ được phát hiện khi mã đang được biên dịch 2 ; Chỉ trong giai đoạn biên dịch, bất kỳ thông tin loại nào cũng được liên kết với một đối tượng.

Vấn đề # 2: printflà một hàm matrixdic ; nó nhận một tham số cố định của loại const char * restrict(chuỗi định dạng), cùng với 0 hoặc nhiều tham số bổ sung, số lượng và loại không được biết tại thời điểm biên dịch:

int printf( const char * restrict fmt, ... );

Các printfchức năng không có cách nào để biết những gì số lượng và loại của các đối số bổ sung là từ những lập luận thông qua bản thân; nó phải dựa vào chuỗi định dạng để cho nó biết cách diễn giải bùn của byte trên ngăn xếp (hoặc trong các thanh ghi). Thậm chí tốt hơn, bởi vì đó là một hàm biến đổi, các đối số với một số loại nhất định được quảng cáo thành một tập hợp các loại mặc định hạn chế (ví dụ: shortđược thăng cấp int, floatđược thăng cấp double, v.v.).

Một lần nữa, không có thông tin liên quan đến chính các đối số bổ sung để đưa ra printfbất kỳ manh mối nào về cách diễn giải hoặc định dạng chúng. Do đó, cần phải có các chỉ định chuyển đổi trong chuỗi định dạng.

Lưu ý rằng ngoài việc cho printfbiết số lượng và loại đối số bổ sung, các công cụ xác định chuyển đổi cũng cho biết printfcách định dạng đầu ra (độ rộng trường, độ chính xác, phần đệm, chứng minh, cơ sở (thập phân, bát phân hoặc hex cho các kiểu số nguyên), v.v.).

Biên tập

Để tránh thảo luận rộng rãi trong các bình luận (và vì trang trò chuyện bị chặn khỏi hệ thống làm việc của tôi - vâng tôi là một đứa trẻ hư), tôi sẽ giải quyết hai câu hỏi cuối ở đây.

NẾU tôi làm điều này:
float b;          
float c;           
b=3.1;    
c=(5.0/9.0)*(b);
Trong câu lệnh cuối làm thế nào trình biên dịch biết rằng b là kiểu float?

Trong quá trình dịch, trình biên dịch duy trì một bảng (thường được gọi là bảng ký hiệu ) lưu trữ thông tin về tên, loại, thời lượng lưu trữ, phạm vi, v.v. Bạn đã khai báo bcnhư floatvậy, bất cứ khi nào trình biên dịch nhìn thấy một biểu thức có bhoặc ctrong đó, nó sẽ tạo mã máy để xử lý giá trị dấu phẩy động.

Tôi lấy mã của bạn ở trên và gói một chương trình đầy đủ xung quanh nó:

/**
 * c1.c
 */
#include <stdio.h>
int main( void )
{
  float b;
  float c;
  b = 3.1;
  c = (5.0 / 9.0) * b;

  printf( "c = %f\n", c );
  return 0;
}

Tôi đã sử dụng -g-Wa,-aldhcác tùy chọn với gcc để tạo danh sách mã máy được tạo xen kẽ với mã nguồn C 3 :

GAS LISTING /tmp/ccmGgGG2.s                     page 1

   1                            .file   "c1.c"
   9                    .Ltext0:
  10                            .section        .rodata
  11                    .LC2:
  12 0000 63203D20              .string "c = %f\n"
  12      25660A00
  13                            .align 8
  14                    .LC1:
  15 0008 721CC771              .long   1908874354
  16 000c 1CC7E13F              .long   1071761180
  17                            .text
  18                    .globl main
  20                    main:
  21                    .LFB2:
  22                            .file 1 "c1.c"
   1:c1.c          **** #include <stdio.h>
   2:c1.c          **** int main( void )
   3:c1.c          **** {
  23                            .loc 1 3 0
  24 0000 55                    pushq   %rbp
  25                    .LCFI0:
  26 0001 4889E5                movq    %rsp, %rbp
  27                    .LCFI1:
  28 0004 4883EC10              subq    $16, %rsp
  29                    .LCFI2:
   4:c1.c          ****   float b;
   5:c1.c          ****   float c;
   6:c1.c          ****   b = 3.1;
  30                            .loc 1 6 0
  31 0008 B8666646              movl    $0x40466666, %eax
  31      40
  32 000d 8945F8                movl    %eax, -8(%rbp)
   7:c1.c          ****   c = (5.0 / 9.0) * b;
  33                            .loc 1 7 0
  34 0010 F30F5A4D              cvtss2sd        -8(%rbp), %xmm1
  34      F8
  35 0015 F20F1005              movsd   .LC1(%rip), %xmm0
  35      00000000
  36 001d F20F59C1              mulsd   %xmm1, %xmm0
  37 0021 F20F5AC0              cvtsd2ss        %xmm0, %xmm0
  38 0025 F30F1145              movss   %xmm0, -4(%rbp)
  38      FC
   8:c1.c          ****
   9:c1.c          ****   printf( "c = %f\n", c );
  39                            .loc 1 9 0
  40 002a F30F5A45              cvtss2sd        -4(%rbp), %xmm0
  40      FC
  41 002f BF000000              movl    $.LC2, %edi
  41      00
  42 0034 B8010000              movl    $1, %eax
  42      00
  43 0039 E8000000              call    printf
  43      00
  10:c1.c          ****   return 0;
  44                            .loc 1 10 0
  45 003e B8000000              movl    $0, %eax

GAS LISTING /tmp/ccmGgGG2.s                     page 2

  11:c1.c          **** }
  46                            .loc 1 11 0
  47 0043 C9                    leave
  48 0044 C3                    ret

Dưới đây là cách đọc danh sách lắp ráp:

  40 002a F30F5A45              cvtss2sd        -4(%rbp), %xmm0
  40      FC
  ^  ^    ^                     ^               ^
  |  |    |                     |               |
  |  |    |                     |               +-- Instruction operands
  |  |    |                     +------------------ Instruction mnemonic
  |  |    +---------------------------------------- Actual machine code (instruction and operands)
  |  +--------------------------------------------- Byte offset of instruction from subroutine entry point
  +------------------------------------------------ Line number of assembly listing

Một điều cần lưu ý ở đây. Trong mã lắp ráp được tạo, không có ký hiệu cho bhoặc c; chúng chỉ tồn tại trong danh sách mã nguồn. Khi mainthực thi trong thời gian chạy, không gian cho bc(cùng với một số nội dung khác) được phân bổ từ ngăn xếp bằng cách điều chỉnh con trỏ ngăn xếp:

subq    $16, %rsp

Mã tham chiếu đến các đối tượng đó bằng phần bù của chúng từ con trỏ khung 4 , với b-8 byte từ địa chỉ được lưu trong con trỏ khung và clà -4 byte từ nó, như sau:

   7:c1.c          ****   c = (5.0 / 9.0) * b;
  .loc 1 7 0
  cvtss2sd        -8(%rbp), %xmm1  ;; converts contents of b from single- to double-
                                   ;; precision float, stores result to floating-
                                   ;; point register xmm1
  movsd   .LC1(%rip), %xmm0        ;; writes the pre-computed value of 5.0/9.0  
                                   ;; to floating point register xmm0
  mulsd   %xmm1, %xmm0             ;; multiply contents of xmm1 by xmm0, store result
                                   ;; in xmm0
  cvtsd2ss        %xmm0, %xmm0     ;; convert result in xmm0 from double- to single-
                                   ;; precision float
  movss   %xmm0, -4(%rbp)          ;; save result to c

Vì bạn đã khai báo bcdưới dạng float, trình biên dịch đã tạo mã máy để xử lý cụ thể các giá trị dấu phẩy động; các movsd, mulsd, cvtss2sdhướng dẫn đều là đặc trưng cho hoạt động nổi-điểm, và các thanh ghi %xmm0%xmm1được sử dụng để lưu trữ gấp đôi độ chính xác giá trị dấu chấm động.

Nếu tôi thay đổi mã nguồn sao cho bclà số nguyên thay vì số float, trình biên dịch sẽ tạo mã máy khác nhau:

/**
 * c2.c
 */
#include <stdio.h>
int main( void )
{
  int b;
  int c;
  b = 3;
  c = (9 / 4) * b; // changed these values since integer 5/9 == 0, making for
                   // some really boring machine code.

  printf( "c = %d\n", c );
  return 0;
}

Biên dịch với gcc -o c2 -g -std=c99 -pedantic -Wall -Werror -Wa,-aldh=c2.lst c2.ccho:

GAS LISTING /tmp/ccyxHwid.s                     page 1

   1                            .file   "c2.c"
   9                    .Ltext0:
  10                            .section        .rodata
  11                    .LC0:
  12 0000 63203D20              .string "c = %d\n"
  12      25640A00
  13                            .text
  14                    .globl main
  16                    main:
  17                    .LFB2:
  18                            .file 1 "c2.c"
   1:c2.c          **** #include <stdio.h>
   2:c2.c          **** int main( void )
   3:c2.c          **** {
  19                            .loc 1 3 0
  20 0000 55                    pushq   %rbp
  21                    .LCFI0:
  22 0001 4889E5                movq    %rsp, %rbp
  23                    .LCFI1:
  24 0004 4883EC10              subq    $16, %rsp
  25                    .LCFI2:
   4:c2.c          ****   int b;
   5:c2.c          ****   int c;
   6:c2.c          ****   b = 3;
  26                            .loc 1 6 0
  27 0008 C745F803              movl    $3, -8(%rbp)
  27      000000
   7:c2.c          ****   c = (9 / 4) * b;
  28                            .loc 1 7 0
  29 000f 8B45F8                movl    -8(%rbp), %eax
  30 0012 01C0                  addl    %eax, %eax
  31 0014 8945FC                movl    %eax, -4(%rbp)
   8:c2.c          ****
   9:c2.c          ****   printf( "c = %d\n", c );
  32                            .loc 1 9 0
  33 0017 8B75FC                movl    -4(%rbp), %esi
  34 001a BF000000              movl    $.LC0, %edi
  34      00
  35 001f B8000000              movl    $0, %eax
  35      00
  36 0024 E8000000              call    printf
  36      00
  10:c2.c          ****   return 0;
  37                            .loc 1 10 0
  38 0029 B8000000              movl    $0, %eax
  38      00
  11:c2.c          **** }
  39                            .loc 1 11 0
  40 002e C9                    leave
  41 002f C3                    ret

Đây là hoạt động tương tự, nhưng với bvà được ckhai báo là số nguyên:

   7:c2.c          ****   c = (9 / 4) * b;
  .loc 1 7 0
  movl    -8(%rbp), %eax  ;; copy value of b to register eax
  addl    %eax, %eax      ;; since 9/4 == 2 (integer arithmetic), double the
                          ;; value in eax
  movl    %eax, -4(%rbp)  ;; write result to c

Đây là những gì tôi muốn nói trước đó khi tôi nói rằng thông tin loại đã được "nướng" vào mã máy. Khi chương trình chạy, nó không kiểm tra bhoặc cxác định loại của chúng; họ đã biết loại của họ nên dựa trên mã máy được tạo.

Nếu trình biên dịch xác định loại và kích thước trong thời gian chạy thì tại sao chương trình sau không hoạt động:
float b='H';         
printf(" value of b is %c \n",b);

Nó không hoạt động vì bạn đang nói dối với trình biên dịch. Bạn nói với nó blà a float, vì vậy nó sẽ tạo mã máy để xử lý các giá trị dấu phẩy động. Khi bạn khởi tạo nó, mẫu bit tương ứng với hằng số 'H'sẽ được hiểu là giá trị dấu phẩy động, không phải giá trị ký tự.

Bạn nói dối trình biên dịch một lần nữa khi bạn sử dụng trình %cxác định chuyển đổi, dự kiến ​​giá trị của kiểu char, cho đối số b. Vì điều này, printfsẽ không diễn giải bchính xác nội dung và bạn sẽ kết thúc với đầu ra rác 5 . Một lần nữa, printfkhông thể biết số lượng hoặc loại của bất kỳ đối số bổ sung nào dựa trên chính các đối số đó; tất cả những gì nó nhìn thấy là một địa chỉ trên ngăn xếp (hoặc một loạt các thanh ghi). Nó cần chuỗi định dạng để cho nó biết những đối số bổ sung nào đã được thông qua và loại của chúng là gì.


1. Một ngoại lệ là mảng có độ dài thay đổi; vì kích thước của chúng không được thiết lập cho đến thời gian chạy, không có cách nào để đánh giá sizeofvề VLA tại thời điểm biên dịch.

2. Kể từ C89, dù sao đi nữa. Trước đó, trình biên dịch chỉ có thể bắt các lỗi không khớp trong kiểu trả về hàm; nó không thể phát hiện sự không phù hợp trong danh sách tham số chức năng.

3. Mã này được tạo trên hệ thống SuSE Linux Enterprise 10 64 bit bằng gcc 4.1.2. Nếu bạn đang thực hiện một cách triển khai khác (kiến trúc trình biên dịch / hệ điều hành / chip), thì các hướng dẫn chính xác của máy sẽ khác, nhưng điểm chung vẫn sẽ giữ; trình biên dịch sẽ tạo ra các hướng dẫn khác nhau để xử lý float và ints so với chuỗi, v.v.

4. Khi bạn gọi một hàm trong chương trình đang chạy, khung stackđược tạo để lưu trữ các đối số hàm, biến cục bộ và địa chỉ của lệnh theo lệnh gọi hàm. Một thanh ghi đặc biệt gọi là con trỏ khung được sử dụng để theo dõi khung hiện tại.

5. Ví dụ, giả sử một hệ thống cuối lớn trong đó byte thứ tự cao là byte được đánh địa chỉ. Mẫu bit cho Hsẽ được lưu trữ bdưới dạng 0x00000048. Tuy nhiên, vì trình %cxác định chuyển đổi chỉ ra rằng đối số phải là a char, nên chỉ đọc byte đầu tiên, do đó printfsẽ cố gắng viết ký tự tương ứng với mã hóa 0x00.


Nếu C là một ngôn ngữ được gõ tĩnh, vậy làm thế nào để putchar () in đúng kiểu dữ liệu mà không đề cập đến nó?
dùng106313

@ user31782: Định nghĩa của putcharhàm nói rằng nó mong đợi 1 đối số kiểu int; khi trình biên dịch tạo mã máy, mã máy đó sẽ cho rằng nó luôn nhận được đối số nguyên duy nhất đó. Không cần chỉ định loại lúc chạy.
John Bode

Tôi có thể in bảng chữ cái bằng cách sử dụng putchar ().
dùng106313

2
@ user31782: printfđịnh dạng tất cả đầu ra của nó dưới dạng văn bản (ASCII hoặc cách khác); công cụ xác định chuyển đổi cho nó biết cách định dạng đầu ra. printf( "%d\n", 65 );sẽ viết chuỗi ký tự '6''5'vào đầu ra tiêu chuẩn, bởi vì bộ %dxác định chuyển đổi yêu cầu nó định dạng đối số tương ứng dưới dạng số nguyên thập phân. printf( "%c\n", 65 );sẽ ghi ký tự 'A'vào đầu ra tiêu chuẩn, vì %cyêu printfcầu định dạng đối số dưới dạng một ký tự từ bộ ký tự thực thi.
John Bode

1
@ user31782: Không phải không có thay đổi về định nghĩa ngôn ngữ. Chắc chắn, đó là có thể (C ++ cũng là tĩnh đánh máy, nhưng có thể suy ra các loại cho <<>>I / O khai thác), nhưng nó sẽ thêm một số phức tạp về ngôn ngữ. Quán tính đôi khi khó vượt qua.
John Bode

8

Bởi vì tại thời điểm khi printfđược gọi và thực hiện công việc của nó, trình biên dịch không còn ở đó để nói cho nó biết phải làm gì.

Hàm không nhận được bất kỳ thông tin nào ngoại trừ thông số trong tham số của nó và tham số vararg không có bất kỳ loại nào, vì vậy printfsẽ không biết cách in bất kỳ nếu chúng không nhận được hướng dẫn rõ ràng qua chuỗi định dạng. Trình biên dịch có thể (thường) suy ra loại đối số là gì, nhưng bạn vẫn sẽ phải viết chuỗi định dạng để cho nó biết nơi in từng đối số liên quan đến văn bản không đổi. So sánh "$%d""%d$"; họ làm những việc khác nhau và trình biên dịch không thể đoán bạn muốn gì. Vì dù sao bạn cũng phải soạn một chuỗi định dạng để chỉ định vị trí đối số , nên đây cũng là một lựa chọn rõ ràng để giảm tải nhiệm vụ nêu rõ các loại đối số cho người dùng.

Thay thế sẽ là trình biên dịch quét chuỗi định dạng cho các vị trí, sau đó suy ra các kiểu, viết lại chuỗi định dạng để thêm thông tin loại và sau đó biên dịch chuỗi đã thay đổi thành nhị phân của bạn. Nhưng điều đó sẽ chỉ làm việc cho các chuỗi định dạng theo nghĩa đen ; C cũng cho phép các chuỗi định dạng được gán động và sẽ luôn có trường hợp trình biên dịch không thể xây dựng lại chính xác chuỗi định dạng sẽ diễn ra trong thời gian chạy. (Ngoài ra, đôi khi bạn muốn in một cái gì đó như một loại khác, có liên quan, thực hiện hiệu quả việc thu hẹp; đó cũng là điều mà không trình biên dịch nào có thể dự đoán được.)


Vì vậy, hàm printf () biết rằng "x" là một biến nhưng nó không biết loại của nó là gì, nhưng trình biên dịch biết. Chúng tôi không thể cập nhật printf () để nó có thể biết loại dữ liệu. Ngoài ra, tôi nhớ rằng trong C ++, "cout" có thể in dữ liệu bằng cách tự động biết loại của nó.
dùng106313

@ user31782 Chức năng gọi C cực kỳ đơn giản. Tất cả những gì printf()được thông qua là một con trỏ tới chuỗi định dạng và một con trỏ tới bộ đệm nơi có thể tìm thấy các đối số. Thậm chí không có chiều dài của bộ đệm này được thông qua! Đây là một trong những lý do C có thể nhanh hơn rất nhiều so với các ngôn ngữ khác. Những gì bạn đang đề xuất là thứ tự cường độ phức tạp hơn.
grahamparks

@grahamparks Là "cout" trong C ++ chậm hơn "printf". Làm printf () như "cout" sẽ làm cho nó chậm?
dùng106313

3
@ user31782: không nhất thiết phải chậm hơn trong thời gian chạy (nó phụ thuộc, như thường lệ), nhưng nó yêu cầu các tính năng ngôn ngữ đơn giản là không có trong C. C không có chức năng nạp chồng, chứ đừng nói đến các cơ chế mẫu được sử dụng couttrong C ++.
Mat

5
Có, nhưng bạn nên nhớ rằng C harks từ năm 1972. Những tính năng này chỉ được phát minh ra sau đó rất nhiều.
Kilian Foth

5

printf()là cái được gọi là hàm matrixdic , là hàm chấp nhận số lượng đối số thay đổi.

Các hàm biến thể trong C sử dụng một nguyên mẫu đặc biệt để báo cho trình biên dịch rằng danh sách các đối số có độ dài không xác định:

int printf(const char *format, ...);

Tiêu chuẩn C cung cấp một tập hợp các hàm trong stdarg.hđó có thể được sử dụng để truy xuất từng đối số một và đưa chúng vào một kiểu nhất định. Điều này có nghĩa là các hàm matrixdic phải tự quyết định loại của từng đối số. printf()đưa ra quyết định này dựa trên những gì trong chuỗi định dạng.

Đây là một sự đơn giản hóa quá mức về cách thức printf()thực sự hoạt động, nhưng quá trình diễn ra như sau:

int printf(const char *format, ...) {

    /* Get ready to process arguments that follow 'format' */
    va_list ap;
    va_start(ap, format);

    /* Deep in the function, something that's dissected the
       format string has decided that the next argument is a
       string.  Grab the next argument, cast it to char * and
       write it to wherever it should go.
     */
    char *string = va_arg(ap, char *);
    write_string_to_output(string);

    /* Conclude processing of arguments */
    va_end(ap);
}

Quá trình tương tự xảy ra cho tất cả các loại printf()có khả năng chuyển đổi. Bạn có thể thấy một ví dụ về điều này trong mã nguồn để triển khai OpenBSD vfprintf(), đó là chức năng nền tảng printf().

Một số trình biên dịch C đủ thông minh để phát hiện các cuộc gọi đến printf(), đánh giá chuỗi định dạng nếu đó là hằng số và xác minh rằng các loại đối số còn lại tương thích với các chuyển đổi được chỉ định. Hành vi này là không bắt buộc, đó là lý do tại sao tiêu chuẩn vẫn yêu cầu cung cấp loại như một phần của chuỗi định dạng. Trước khi các loại kiểm tra này được thực hiện, sự không khớp giữa chuỗi định dạng và danh sách đối số chỉ tạo ra đầu ra không có thật.

Trong C ++, <<là một nhà điều hành, mà làm cho sử dụng của coutnhư cout << foo << barmột biểu ghi vào có thể được đánh giá cho đúng đắn tại thời gian biên dịch và chuyển thành mã mà phôi các biểu thức bên phải vào một cái gì đó coutcó thể đối phó với.


3

Các nhà thiết kế của C muốn làm cho trình biên dịch đơn giản nhất có thể. Mặc dù có thể xử lý I / O theo nhiều ngôn ngữ khác và yêu cầu trình biên dịch tự động cung cấp thói quen I / O với thông tin về các loại tham số đã truyền và trong nhiều trường hợp, cách tiếp cận như vậy có thể cho phép mã hiệu quả hơn mức có thể với printf(*), xác định những thứ theo cách đó sẽ làm cho trình biên dịch trở nên phức tạp hơn.

Trong những ngày đầu tiên của C, mã được gọi là hàm không biết cũng không quan tâm đến những đối số mà nó đang mong đợi. Mỗi đối số sẽ đẩy một số lượng từ trên ngăn xếp theo loại của nó và các hàm sẽ tìm thấy các tham số khác nhau trong vị trí ngăn xếp trên cùng, từ trên xuống dưới, v.v. bên dưới địa chỉ trả về. Nếu một printfphương thức có thể tìm ra nơi để tìm các đối số của nó trên ngăn xếp, thì không có cách nào để trình biên dịch xử lý nó khác với bất kỳ phương thức nào khác.

Trong thực tế, mẫu tham số truyền được hình dung bởi C rất hiếm khi được sử dụng nữa trừ khi gọi các hàm matrixdic như printf, và nếu printfđược định nghĩa là sử dụng các quy ước truyền tham số đặc biệt [ví dụ: có tham số đầu tiên là trình biên dịch được tạo const char*tự động có chứa trình tạo tự động thông tin về các loại được thông qua], trình biên dịch sẽ có thể tạo mã tốt hơn cho nó (tránh sự cần thiết phải quảng cáo số nguyên và dấu phẩy động trong số những thứ khác).] Thật không may, tôi nhận thấy không có khả năng nào của trình biên dịch thêm tính năng trình biên dịch báo cáo các loại biến để gọi là mã.

Tôi thấy tò mò rằng con trỏ null được coi là "sai lầm tỷ đô", được cung cấp cho tiện ích của chúng và cho rằng chúng thường chỉ gây ra hành vi xấu nghiêm trọng trong các ngôn ngữ không bẫy số học và truy cập con trỏ null. Tôi coi tác hại gây ra bởi printfvà chuỗi kết thúc bằng không là tồi tệ hơn nhiều.


0

Hãy suy nghĩ về nó giống như bạn đang chuyển các biến sang một hàm khác mà bạn đã xác định. Bạn thường nói với chức năng khác loại dữ liệu nào sẽ mong đợi / nhận. Cách tương tự với printf(). Nó đã được xác định trong stdio.hthư viện và yêu cầu bạn cho nó biết dữ liệu nào đang nhận để có thể xuất dữ liệu theo đúng định dạng (như trong trường hợp của bạn int).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.